feat(homepage): add new sections to homepage

This commit is contained in:
Amruth Pillai
2023-11-13 17:03:41 +01:00
parent 4b1e33db80
commit d18b258761
79 changed files with 3096 additions and 313 deletions

View File

@ -58,8 +58,9 @@ REDIS_URL=redis://default:password@localhost:6379
# SENTRY_DSN=
# Crowdin (Optional)
CROWDIN_PROJECT_ID=
CROWDIN_DISTRIBUTION_HASH=
CROWDIN_PERSONAL_TOKEN=
CROWDIN_ACCESS_TOKEN=
# GitHub (OAuth, Optional)
GITHUB_CLIENT_ID=

2
.github/FUNDING.yml vendored
View File

@ -1 +1,3 @@
github: AmruthPillai
open_collective: reactive-resume
custom: https://paypal.me/amruthde

View File

@ -14,14 +14,13 @@ jobs:
uses: actions/checkout@v4.1.1
- name: Sync Sources with Crowdin
uses: crowdin/github-action@v1
uses: crowdin/github-action@v1.14.1
with:
upload_sources: true
create_pull_request: false
upload_translations: false
upload_translations: true
download_translations: false
env:
GITHUB_TOKEN: ${{ github.token }}
CROWDIN_PROJECT_ID: "503410"
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_TOKEN }}
CROWDIN_ACCESS_TOKEN: ${{ secrets.CROWDIN_TOKEN }}

3
.gitignore vendored
View File

@ -48,4 +48,5 @@ libs/prisma
# Lingui Compiled Messages
apps/client/src/locales/_build/
apps/client/src/locales
apps/client/src/locales/*/*
!apps/client/src/locales/en-US/*

View File

@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm exec commitlint --edit $1
pnpm exec commitlint --edit $1

View File

@ -4,6 +4,7 @@
[![Docker Pulls](https://img.shields.io/docker/pulls/amruthpillai/reactive-resume)](https://hub.docker.com/repository/docker/amruthpillai/reactive-resume)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/AmruthPillai)](https://github.com/sponsors/AmruthPillai)
[![Crowdin](https://badges.crowdin.net/reactive-resume/localized.svg)](https://crowdin.com/project/reactive-resume)
![Discord](https://img.shields.io/discord/1173518977851473940?label=discord&link=https%3A%2F%2Fdiscord.gg%2FhzwkZbyvUW)
# Reactive Resume
@ -30,18 +31,21 @@ TODO: Add screenshots of major sections of the app
- **Free, forever** and open-source
- No telemetry, user tracking or advertising
- You can self-host the application in less then 30 seconds
- **Available in 5+ languages**, and counting ([help add your language here](https://translate.rxresu.me/))
- Use your email address (or a throw-away address, no problem) to create an account
- **Available in 20+ languages**, and counting ([help add/improve your language here](https://translate.rxresu.me/))
- Use your email address (or a throw-away address) to create an account
- You can also sign in with your GitHub or Google account, and even set up two-factor authentication for extra security
- Create as many resumes as you like under a single account, optimising each resume for every job application based on it's description for a higher ATS score
- **Bring your own OpenAI API key** and unlock features such as improving your writing, fixing spelling and grammar or changing the tone of your text in one-click
- Translate your resume into any language using ChatGPT and import it back for easier editing
- Create single page resumes or a resume that spans multiple pages easily
- Mix-and-match your resume and make it yours by picking any colour
- Customize the colors and layouts to add a personal touch to your resume
- Customise your page layout as you like just by dragging-and-dropping sections
- Jot down personal notes specific to your resume that's only visible to you
- **Dozens of templates** to choose from, ranging from professional to modern to swanky
- Supports printing resumes in A4 or Letter page formats
- Create your resume with any font hosted by [Google Fonts](https://fonts.google.com/), and a special inclusion of [Computer Modern](https://tug.org/FontCatalogue/computermodern/) to make your resumes look like they're made in LaTeX
- Design your resume with any font family and variant that's available on [Google Fonts](https://fonts.google.com/)
- **Share a personalised link of your resume** to companies or recruiters for them to get the latest updates and as a bonus, you can track how many times your resume has been viewed or downloaded since it's creation
- You can track the number of views or downloads your public resume has received
- Built with state-of-the-art (at the moment) and dependable technologies with the simplest and most human-readable code on all of GitHub
- **MIT License**, so do what you like with the code as long as you credit the original author
- And finally yes, there's a dark mode too 🌓
@ -54,9 +58,10 @@ TODO: Add screenshots of major sections of the app
- Redis (for caching, session storage and resume statistics)
- Minio (for object storage: to store avatars, resume PDFs and previews)
- Browserless (for headless chrome, to print PDFs and generate previews)
- Optional: An SMTP Server (to send password recovery emails)
- Optional: Sentry (for error tracing and performance monitoring)
- Optional: GitHub/Google OAuth Token (for quickly authenticating users)
- SMTP Server (to send password recovery emails)
- Sentry (for error tracing and performance monitoring)
- GitHub/Google OAuth (for quickly authenticating users)
- LinguiJS and Crowdin (for translation management and localization)
## Star History
@ -68,60 +73,6 @@ TODO: Add screenshots of major sections of the app
</picture>
</a>
## Frequently Asked Questions
<details>
<summary><strong>Who are you, and why did you build Reactive Resume?</strong></summary>
I'm Amruth Pillai, just another run-off-the-mill developer working at Elara Digital GmbH in Berlin, Germany. I'm married to my beautiful and insanely supportive wife who has helped me in more ways than one in seeing this project to it's fruition. I am originally from Bengaluru, India where I was a developer at Postman (the API testing tool) for a short while. My hobbies include eating, living and breathing code.
Back in my university days, I designed a really cool dark mode resume (link on my website) using Figma and I had a line of friends and strangers asking me to design their resume for them.
While I could have charged everyone a hefty sum and retired even before I began, I decided to build the first version of Reactive Resume in 2019. Since then, it's gone through multiple iterations as I've learned a lot of better coding practices over the years.
At the time of writing, Reactive Resume is probably one of the only handful of resume builders out there available to the world for free and without an annoying paywall at the end. While being free is often associated with software that's not of good quality, I strive to prove them wrong and build a product that people love using and are benefitted by it.
My dream has always been to build something that at least a handful people use on a daily basis, and I'm extremely proud to say that Reactive Resume, over it's years of development, has **helped over half a million people build their resume**, and I hope it only increases from here and reaches more people who are in need of a good resume to kickstart their career but can't afford to pay for one.
</details>
<details>
<summary><strong>How much does it cost to run Reactive Resume?</strong></summary>
It's not much honestly. DigitalOcean has graciously sponsored their infrastructure to allow me to host Reactive Resume on their platform. There's a small fee I pay to dependent services, to send emails for example, and the most of it goes to managing the domain and other related apps (documentation).
I've spent countless hours and sleepless nights building the application though, and I honestly do not expect anything in return but to hear from you on how the app has helped you with your career.
But if you do feel like supporting the developer and the future development of Reactive Resume, please donate (_only if you have some extra money lying around_) to me on my [GitHub Sponsors page](https://github.com/sponsors/AmruthPillai/). You can choose to donate one-time or sponsor a recurring donation.
</details>
<details>
<summary><strong>Other than donating, how can I support you?</strong></summary>
**If you speak a language other than English**, sign up to be a translator on Crowdin, our translation management service. You can help translate the product to your language and share it among your community. Even if the language is already translated, it helps to sign up as you would be notified when there are new phrases to be translated.
**If you work in the media, are an influencer or have lots of friends**, share the app with your circles and let them know so it can reach the people who need it the most. Im also [open for interviews](mailto:hello@amruthpillai.com), although thats wishful thinking. If you do mention Reactive Resume on your blog, let me know so that I can link back to you here.
**If you found a bug or have an idea for a feature**, raise an issue on GitHub or shoot me a message and let me know what youd like to see. I cant promise that itll be done soon, but juggling work, life and open-source, Ill definitely get to it when I can.
</details>
<details>
<summary><strong>How does the OpenAI Integration work? How can I trust you with my API key?</strong></summary>
You should **absolutely not** trust me with your OpenAI API key.
OpenAI has been a game-changer for all of us. I cannot tell you how much ChatGPT has helped me in my everyday work and with the development of Reactive Resume. It only makes sense that you leverage what AI has to offer and let it help you build the perfect resume.
While most applications out there charge you a fee to use their AI services (rightfully so, because it isnt cheap), you can choose to enter your own OpenAI API key on the Settings page (under OpenAI Integration). The key is stored in your browsers local storage, which means that if you uninstall your browser, or even clear your data, the key is gone with it. All requests made to OpenAI are also sent directly to their service and does not hit the app servers at all.
The policy behind “Bring your own Key” (BYOK) is [still being discussed](https://community.openai.com/t/openais-bring-your-own-key-policy/14538/46) and probably might change over a period of time, but while its available, I would keep the feature on the app.
You are free to turn off all AI features (and not be aware of its existence) simply by not adding a key in the Settings page and still make use of all of the useful features that Reactive Resume has to offer. I would even suggest you to take the extra step of using ChatGPT to write your content, and simply copy it over to Reactive Resume.
</details>
## License
Reactive Resume is packaged and distributed using the [MIT License](/LICENSE.md) which allows for commercial use, distribution, modification and private use provided that all copies of the software contain the same license and copyright.

View File

@ -45,8 +45,14 @@ export const ArtboardPage = () => {
);
}, [metadata]);
// Underline Links
// Typography Options
useEffect(() => {
if (metadata.typography.hideIcons) {
document.querySelector("#root")!.classList.add("hide-icons");
} else {
document.querySelector("#root")!.classList.remove("hide-icons");
}
if (metadata.typography.underlineLinks) {
document.querySelector("#root")!.classList.add("underline-links");
} else {

View File

@ -10,6 +10,10 @@
@apply antialiased;
}
#root.hide-icons .ph {
@apply hidden;
}
#root.underline-links a {
@apply underline underline-offset-2;
}

View File

@ -230,6 +230,7 @@ const Profiles = () => {
label={item.username}
icon={
<img
className="ph"
width={fontSize}
height={fontSize}
alt={item.network}

View File

@ -207,6 +207,7 @@ const Profiles = () => {
label={item.username}
icon={
<img
className="ph"
width={fontSize}
height={fontSize}
alt={item.network}

View File

@ -27,6 +27,7 @@ import { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
const profiles = useArtboardStore((state) => state.resume.sections.profiles);
const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size);
return (
<div className="grid grid-cols-3">
@ -83,8 +84,9 @@ const Header = () => {
className="text-sm"
icon={
<img
width="12"
height="12"
className="ph"
width={fontSize}
height={fontSize}
alt={item.network}
src={`https://cdn.simpleicons.org/${item.icon}`}
/>

View File

@ -79,13 +79,13 @@ const Header = () => {
</>
)}
{basics.customFields.map((item) => (
<>
<div key={item.id} className="flex items-center gap-x-1.5">
<Fragment key={item.id}>
<div className="flex items-center gap-x-1.5">
<i className={cn(`ph ph-bold ph-${item.icon}`, "text-primary")} />
<span>{[item.name, item.value].filter(Boolean).join(": ")}</span>
</div>
<div className="bg-text h-1 w-1 rounded-full last:hidden" />
</>
</Fragment>
))}
</div>
</div>
@ -234,6 +234,7 @@ const Profiles = () => {
label={item.username}
icon={
<img
className="ph"
width={fontSize}
height={fontSize}
alt={item.network}

View File

@ -27,6 +27,7 @@ import { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
const profiles = useArtboardStore((state) => state.resume.sections.profiles);
const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size);
return (
<div className="flex flex-col items-center justify-center space-y-2 pb-2 text-center">
@ -81,8 +82,9 @@ const Header = () => {
className="text-sm"
icon={
<img
width="12"
height="12"
className="ph"
width={fontSize}
height={fontSize}
alt={item.network}
src={`https://cdn.simpleicons.org/${item.icon}`}
/>

View File

@ -27,6 +27,7 @@ import { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
const profiles = useArtboardStore((state) => state.resume.sections.profiles);
const fontSize = useArtboardStore((state) => state.resume.metadata.typography.font.size);
return (
<div className="flex items-center justify-between space-x-4 border-b border-primary pb-5">
@ -73,7 +74,7 @@ const Header = () => {
{profiles.visible && profiles.items.length > 0 && (
<div
className="grid gap-x-4 gap-y-1 self-end text-right"
className="grid gap-x-4 gap-y-1 text-right"
style={{ gridTemplateColumns: `repeat(${profiles.columns}, auto)` }}
>
{profiles.items
@ -86,8 +87,9 @@ const Header = () => {
className="text-sm"
icon={
<img
width="12"
height="12"
className="ph"
width={fontSize}
height={fontSize}
alt={item.network}
src={`https://cdn.simpleicons.org/${item.icon}`}
/>
@ -108,7 +110,7 @@ const Summary = () => {
return (
<section id={section.id}>
<h4 className="mb-1 font-bold uppercase text-primary">{section.name}</h4>
<h4 className="font-bold uppercase text-primary">{section.name}</h4>
<div
className="wysiwyg"
@ -180,7 +182,7 @@ const Section = <T,>({
return (
<section id={section.id} className="grid">
<h4 className="mb-1 font-bold uppercase text-primary">{section.name}</h4>
<h4 className="font-bold uppercase text-primary">{section.name}</h4>
<div
className="grid gap-3"

View File

@ -81,13 +81,13 @@ const Header = () => {
</>
)}
{basics.customFields.map((item) => (
<>
<div key={item.id} className="flex items-center gap-x-1.5">
<Fragment key={item.id}>
<div className="flex items-center gap-x-1.5">
<i className={cn(`ph ph-bold ph-${item.icon}`)} />
<span>{[item.name, item.value].filter(Boolean).join(": ")}</span>
</div>
<div className="h-1 w-1 rounded-full bg-background last:hidden" />
</>
</Fragment>
))}
</div>
</div>
@ -234,6 +234,7 @@ const Profiles = () => {
label={item.username}
icon={
<img
className="ph"
width={fontSize}
height={fontSize}
alt={item.network}

View File

@ -206,6 +206,7 @@ const Profiles = () => {
label={item.username}
icon={
<img
className="ph"
width={fontSize}
height={fontSize}
alt={item.network}

View File

@ -15,11 +15,13 @@ declare namespace Cypress {
login(email: string, password: string): void;
}
}
//
// -- This is a parent command --
Cypress.Commands.add("login", (email, password) => {
console.log("Custom command example: Login", email, password);
});
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })

View File

@ -20,7 +20,13 @@
"react-hooks/exhaustive-deps": "off",
// lingui
"lingui/no-unlocalized-strings": 2,
"lingui/no-unlocalized-strings": [
2,
{
"ignoreFunction": ["cn"],
"ignoreAttribute": ["alt"]
}
],
"lingui/t-call-in-function": 2,
"lingui/no-single-variables-to-translate": 2,
"lingui/no-expression-in-message": 2,

View File

@ -39,10 +39,5 @@
<!-- Scripts -->
<script type="module" src="/src/main.tsx"></script>
<!-- Crowdin Anonymous Translations -->
<script type="text/javascript" src="https://crowdin.com/js/crowdjet/crowdjet.js"></script>
<div id="crowdjet-container" data-project-id="503410" style="bottom: 90px; right: 20px"></div>
<div id="crowdjet-expand-container" style="bottom: 10px; right: 20px"></div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 KiB

Binary file not shown.

View File

@ -0,0 +1,23 @@
<svg width="730" height="151" viewBox="0 0 730 151" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_450_81)">
<path d="M188.89 81.5672C188.89 92.7163 192.303 101.654 199.123 108.394C205.95 115.127 215.173 118.497 226.807 118.497C238.442 118.497 246.271 115.65 252.088 109.963C257.905 104.233 260.814 97.5844 260.814 90.0317V89.3461H243.981V89.9607C243.981 93.0061 242.651 95.6271 239.999 97.8102C237.339 99.9933 232.902 101.089 226.672 101.089C219.668 101.089 214.533 99.3787 211.284 95.9664C208.033 92.5537 206.405 88.0038 206.405 82.3164V78.6284C206.405 72.9406 208.055 68.3907 211.355 64.9784C214.654 61.5657 219.76 59.8561 226.672 59.8561C232.717 59.8561 237.112 60.9017 239.864 62.9932C242.659 65.0842 244.053 67.7267 244.053 70.9132V71.5983H260.885L260.814 70.708C260.536 63.1059 257.564 56.5144 251.882 50.9113C246.2 45.2664 237.844 42.4472 226.807 42.4472C215.173 42.4472 205.943 45.8173 199.123 52.5507C192.296 59.2415 188.89 68.1787 188.89 79.3772V81.5603V81.5672Z" fill="#263238"/>
<path d="M274.972 116.448H292.146V80.9948C292.146 73.2304 295.901 69.2173 299.015 67.0269C302.13 64.7944 305.679 63.6852 309.661 63.6852C310.898 63.6852 312.022 63.7557 313.025 63.8899C314.077 64.0245 315.066 64.1866 315.976 64.3707V43.4858C315.521 43.3022 314.717 43.0971 313.572 42.8712C312.47 42.645 311.396 42.5319 310.344 42.5319C306.269 42.5319 302.628 43.6269 299.421 45.81C296.533 47.7742 294.201 50.0988 292.43 52.7623C292.245 53.038 291.812 52.9249 291.79 52.5929L290.29 44.5103H274.965V116.448H274.972Z" fill="#263238"/>
<path d="M322.995 81.5672C322.995 92.7163 326.543 101.654 333.64 108.394C340.737 115.127 350.081 118.497 361.673 118.497C373.264 118.497 382.537 115.127 389.634 108.394C396.781 101.661 400.351 92.7163 400.351 81.5672V79.3841C400.351 68.1929 396.781 59.2484 389.634 52.5576C382.537 45.8242 373.215 42.4541 361.673 42.4541C350.131 42.4541 340.744 45.8242 333.64 52.5576C326.543 59.2484 322.995 68.1856 322.995 79.3841V81.5672ZM340.51 78.6353C340.51 72.4038 342.344 67.8536 346.007 64.9853C349.669 62.1167 354.889 60.6823 361.673 60.6823C368.457 60.6823 373.606 62.1167 377.268 64.9853C380.98 67.8536 382.836 72.4038 382.836 78.6353V82.3233C382.836 88.7879 381.001 93.4014 377.339 96.178C373.719 98.9052 368.5 100.276 361.673 100.276C354.846 100.276 349.605 98.9125 345.943 96.178C342.323 93.4014 340.517 88.781 340.517 82.3233V78.6353H340.51Z" fill="#263238"/>
<path d="M408.151 44.4957L429.722 116.434H449.255L464.575 64.1936C464.737 63.6353 465.532 63.6211 465.71 64.1794L482.23 116.434H501.759L523.468 44.4957H502.229L491.51 84.5628C491.056 86.3855 490.614 88.1798 490.205 89.9534C489.969 91.0695 489.576 92.8432 489.329 93.9665C489.272 94.2138 488.915 94.2207 488.859 93.9665C488.555 92.6806 488.226 91.3452 487.861 89.9461C487.407 88.1234 486.905 86.3287 486.349 84.5555L474.394 44.4888H454.055C454.055 44.4888 442.051 85.361 439.833 93.5072C439.74 93.8603 439.237 93.8534 439.144 93.5072L426.714 44.4957H408.138H408.151Z" fill="#263238"/>
<path d="M529.094 81.5672C529.094 92.2144 532.001 100.926 537.818 107.709C543.676 114.442 551.813 117.812 562.211 117.812C567.616 117.812 572.286 116.837 576.218 114.88C578.959 113.502 582.47 110.414 584.403 108.613C585.194 111.919 586.252 116.455 586.252 116.455H603.279V11.8123H585.356V53.1722C583.07 49.9857 580.045 47.5269 576.291 45.8032C572.537 44.0295 567.976 43.1397 562.621 43.1397C552.547 43.1397 544.439 46.5309 538.301 53.3064C532.167 60.0394 529.094 68.7297 529.094 79.3768V81.5599V81.5672ZM546.616 82.2524L546.551 78.7059C546.551 73.5199 548.152 69.3301 551.359 66.144C554.566 62.9149 559.536 61.2969 566.27 61.2969C570.984 61.2969 574.998 62.3924 578.29 64.5755C578.326 64.5965 578.355 64.6176 578.387 64.6387C582.798 67.5921 585.364 72.5944 585.364 77.8792V82.8248C585.364 88.258 582.656 93.3803 578.055 96.3122C578.018 96.3333 577.982 96.3548 577.949 96.3759C574.422 98.559 570.344 99.654 565.722 99.654C558.94 99.654 554.067 98.036 551.087 94.8074C548.107 91.5787 546.624 87.3888 546.624 82.2455L546.616 82.2524Z" fill="#263238"/>
<path d="M623.619 116.448H640.728V44.5026H623.619V116.441V116.448ZM619.983 24.8473C619.983 27.9418 621.037 30.4924 623.141 32.4918C625.298 34.4489 628.314 35.4239 632.21 35.4239C636.106 35.4239 639.037 34.4206 641.141 32.4212C643.294 30.4217 644.368 27.8924 644.368 24.8473C644.368 21.8022 643.318 19.3435 641.206 17.3369C639.098 15.3375 636.078 14.3342 632.137 14.3342C628.196 14.3342 625.298 15.3375 623.141 17.3369C621.037 19.3364 619.983 21.8446 619.983 24.8473Z" fill="#263238"/>
<path d="M661.472 116.448H678.58V87.2688C678.58 70.7502 684.463 60.2798 697.261 60.2798C710.064 60.2798 712.792 67.6768 712.792 78.0629V116.448H729.896V73.7883C729.896 61.5515 727.379 53.3769 722.336 49.286C717.296 45.146 710.867 43.076 703.03 43.076C697.861 43.076 693.073 44.0295 688.671 45.9442C684.718 47.64 681.495 50.0563 679.014 53.1791C678.807 52.0699 677.68 44.5099 677.68 44.5099H661.464V116.448H661.472Z" fill="#263238"/>
<path d="M107.11 150.245H44.2022C17.4663 150.245 0.166702 133.057 0.166702 106.494V43.5159C0.166702 17.432 17.4663 0.244507 43.7202 0.244507H107.11C133.845 0.244507 151.145 17.432 151.145 43.9946V106.494C151.145 133.057 133.845 150.245 107.11 150.245Z" fill="#263238"/>
<path d="M97.1648 102.44C94.3399 102.44 91.8228 101.569 89.7394 99.8627C87.2502 97.8544 85.2714 94.8592 85.2013 91.3391C85.1664 89.5606 87.0471 89.5606 87.0471 89.5606C87.0471 89.5606 90.1026 89.5249 91.5852 89.5249C93.0674 89.5606 93.5008 91.6845 93.5709 92.2172C94.1441 96.9904 96.7663 99.071 98.7799 100.021C99.9897 100.59 99.6889 102.368 97.1648 102.447V102.44Z" fill="white"/>
<path d="M71.7973 78.1858C69.3081 77.8833 65.6161 77.6243 63.2319 77.0194C59.3652 76.0403 59.4702 72.4622 59.638 71.2963C60.1067 67.7972 61.3513 64.5576 63.2668 61.5483C65.651 57.8624 69.0775 54.6228 73.4822 51.988C81.7468 47.0563 94.4125 43.9943 107.11 43.9943C109.463 43.9943 126.594 43.9943 126.594 47.5099C126.594 49.4632 122.638 49.2812 120.916 49.2812C107.981 49.2812 98.7726 51.088 91.9204 55.0043C85.166 58.8414 80.4604 64.7162 77.2719 73.4125C76.9363 74.1686 75.8246 78.6463 71.7973 78.193V78.1858Z" fill="white"/>
<path d="M83.0818 114.073C76.663 114.21 70.6148 111.222 66.0349 106.01C62.1544 101.597 59.8192 97.1477 59.3855 91.4388C59.1199 87.7095 60.7213 86.4211 62.8955 86.6441C64.3991 86.7953 69.0767 87.0256 71.7544 87.5943C73.7611 88.0119 75.097 89.1568 75.4322 91.511C77.2014 104.066 84.8928 109.005 89.4726 110.07C90.2765 110.257 90.7732 110.79 90.7452 111.748C90.7099 112.662 90.1995 113.914 83.0887 114.066L83.0818 114.073Z" fill="white"/>
<path d="M64.7981 125.219C60.2462 125.413 55.792 124.47 54.5194 124.24C49.1636 123.261 44.6745 121.569 40.8289 119.057C31.6205 113.053 26.0618 102.448 25.3975 89.8926C25.2298 86.9125 24.8592 81.2178 31.7603 81.6499C34.6061 81.808 39.123 83.2192 42.3045 84.1622C46.2548 85.2998 48.1639 88.4384 48.1639 91.5414C48.1639 109.244 63.0747 120.558 70.1518 120.558C71.7739 120.558 72.5109 121.08 72.5109 122.12C72.5109 122.902 71.7759 124.924 64.8049 125.226L64.7981 125.219Z" fill="white"/>
<path d="M41.0445 73.1964C38.3525 72.6994 35.7234 71.5549 33.1644 70.8998C25.2493 68.8768 26.5988 60.6552 27.3399 58.402C34.5418 36.574 57.4059 29.2741 75.8579 27.3231C93.2332 25.4873 111.412 26.9056 128.347 31.7578C129.557 32.089 132.892 32.9169 132.172 34.9326C131.452 36.9484 129.445 35.5446 110.972 35.127C104.882 34.9903 98.799 35.3862 92.6948 36.3509C82.2207 37.9923 71.4528 41.3976 62.7965 48.5822C58.6225 52.0594 54.9515 56.4941 52.4972 61.7352C51.8542 63.1103 51.3507 64.4851 50.9174 65.8602C50.4767 67.3144 48.8267 74.6145 41.0514 73.2033L41.0445 73.1964Z" fill="white"/>
<path d="M85.3408 77.409C86.8229 70.5771 92.1698 60.401 112.615 60.401C114.974 60.401 115.76 61.1822 115.76 61.9634C115.76 62.7446 114.66 63.5263 113.401 63.5263C100.978 63.5263 97.0314 71.1025 94.1648 78.8636C93.2417 81.3617 91.1373 81.7359 88.5013 81.2895C86.6555 80.951 84.6487 80.728 85.3408 77.409Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_450_81">
<rect width="729.73" height="150" fill="white" transform="translate(0.166702 0.244507)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@ -0,0 +1,23 @@
<svg width="731" height="151" viewBox="0 0 731 151" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_450_96)">
<path d="M189.619 80.681C189.619 91.7425 192.992 100.61 199.738 107.294C206.484 113.974 215.605 117.318 227.106 117.318C238.606 117.318 246.345 114.497 252.098 108.851C257.846 103.162 260.722 96.5725 260.722 89.0773V88.4027H244.084V89.0112C244.084 92.0348 242.769 94.6293 240.144 96.7991C237.518 98.9639 233.128 100.049 226.968 100.049C220.043 100.049 214.971 98.3554 211.754 94.9687C208.537 91.5819 206.934 87.0677 206.934 81.4265V77.7706C206.934 72.1293 208.566 67.6152 211.825 64.2284C215.085 60.8416 220.132 59.1483 226.968 59.1483C232.943 59.1483 237.291 60.1857 240.007 62.2614C242.769 64.3366 244.151 66.9547 244.151 70.1149V70.7943H260.788L260.722 69.9122C260.453 62.3745 257.505 55.8272 251.895 50.2756C246.279 44.6765 238.019 41.8792 227.106 41.8792C215.605 41.8792 206.484 45.2189 199.738 51.9029C192.992 58.5398 189.619 67.4076 189.619 78.5161V80.681Z" fill="white"/>
<path d="M274.711 115.284H291.69V80.1146C291.69 72.4119 295.403 68.426 298.478 66.2607C301.558 64.0484 305.068 62.9449 309.004 62.9449C310.225 62.9449 311.336 63.011 312.33 63.1476C313.371 63.2842 314.345 63.4399 315.249 63.6195V42.8976C314.795 42.7184 314.005 42.5157 312.874 42.2891C311.786 42.0629 310.721 41.9493 309.681 41.9493C305.65 41.9493 302.05 43.0342 298.88 45.1995C296.028 47.1523 293.724 49.4493 291.969 52.1003C291.789 52.3739 291.358 52.2608 291.34 51.9304L289.854 43.9119H274.706V115.284H274.711Z" fill="white"/>
<path d="M322.184 80.681C322.184 91.7425 325.694 100.61 332.71 107.294C339.726 113.974 348.965 117.318 360.418 117.318C371.871 117.318 381.039 113.978 388.06 107.294C395.123 100.615 398.652 91.7425 398.652 80.681V78.5161C398.652 67.412 395.118 58.5398 388.06 51.9029C381.044 45.2234 371.829 41.8792 360.418 41.8792C349.008 41.8792 339.726 45.2189 332.71 51.9029C325.694 58.5398 322.184 67.4076 322.184 78.5161V80.681ZM339.504 77.7706C339.504 71.5869 341.315 67.0727 344.934 64.2284C348.558 61.3841 353.719 59.9639 360.418 59.9639C367.117 59.9639 372.212 61.3841 375.836 64.2284C379.502 67.0727 381.337 71.5869 381.337 77.7706V81.4265C381.337 87.8368 379.526 92.417 375.907 95.1718C372.33 97.8791 367.169 99.2331 360.423 99.2331C353.677 99.2331 348.492 97.8791 344.873 95.1718C341.296 92.417 339.508 87.8368 339.508 81.4265V77.7706H339.504Z" fill="white"/>
<path d="M406.367 43.9071L427.695 115.28H447.009L462.155 63.4545C462.313 62.8979 463.103 62.8886 463.278 63.4448L479.611 115.289H498.913L520.371 43.9168H499.371L488.778 83.6668C488.323 85.4733 487.894 87.2563 487.484 89.0158C487.253 90.1241 486.868 91.8836 486.625 92.9968C486.572 93.2421 486.219 93.247 486.163 92.9968C485.863 91.7235 485.534 90.3933 485.178 89.0109C484.723 87.2044 484.225 85.4214 483.682 83.662L471.864 43.912H451.764C451.764 43.912 439.894 84.459 437.709 92.5395C437.615 92.8886 437.117 92.8837 437.028 92.5347L424.736 43.912H406.375L406.367 43.9071Z" fill="white"/>
<path d="M525.917 80.6818C525.917 91.243 528.795 99.8891 534.54 106.616C540.337 113.295 548.372 116.639 558.653 116.639C563.996 116.639 568.614 115.667 572.506 113.729C575.21 112.365 578.684 109.295 580.594 107.507C581.372 110.79 582.422 115.285 582.422 115.285H599.246V11.4788H581.538V52.5118C579.276 49.3512 576.284 46.9127 572.571 45.2003C568.861 43.4408 564.353 42.5591 559.063 42.5591C549.098 42.5591 541.083 45.9223 535.018 52.6484C528.949 59.3279 525.917 67.9504 525.917 78.5165V80.6818ZM543.236 81.356L543.171 77.8371C543.171 72.6908 544.756 68.5354 547.918 65.3798C551.088 62.1771 556.006 60.5733 562.663 60.5733C567.321 60.5733 571.286 61.6581 574.545 63.823C574.578 63.8465 574.61 63.8656 574.642 63.8891C579.001 66.8185 581.538 71.7852 581.538 77.0258V81.9316C581.538 87.3183 578.855 92.4033 574.31 95.3137C574.273 95.3372 574.237 95.3562 574.205 95.3798C570.718 97.5446 566.688 98.6299 562.115 98.6299C555.414 98.6299 550.594 97.0261 547.651 93.823C544.707 90.6203 543.236 86.4645 543.236 81.3657V81.356Z" fill="white"/>
<path d="M619.363 115.285H636.272V43.9128H619.363V115.285ZM615.767 24.4126C615.767 27.4834 616.809 30.0117 618.888 31.9975C621.021 33.9409 624.005 34.9079 627.852 34.9079C631.703 34.9079 634.598 33.9126 636.682 31.9268C638.81 29.9409 639.872 27.4362 639.872 24.4126C639.872 21.3891 638.83 18.9503 636.746 16.9645C634.667 14.9787 631.679 13.9834 627.783 13.9834C623.887 13.9834 621.021 14.9787 618.888 16.9645C616.809 18.9503 615.767 21.4315 615.767 24.4126Z" fill="white"/>
<path d="M656.786 115.285H673.695V86.3319C673.695 69.9406 679.505 59.5585 692.165 59.5585C704.826 59.5585 707.518 66.8931 707.518 77.2046V115.285H724.424V72.9641C724.424 60.8226 721.934 52.7189 716.956 48.6527C711.974 44.5443 705.613 42.4922 697.869 42.4922C692.757 42.4922 688.026 43.4404 683.676 45.3365C679.764 47.0206 676.582 49.4121 674.133 52.5158C673.926 51.4168 672.811 43.9168 672.811 43.9168H656.786V115.29V115.285Z" fill="white"/>
<path d="M105.176 150.245H47.5955C17.7228 150.245 0.896423 133.212 0.896423 103.848V46.6407C0.896423 17.277 17.7228 0.244507 47.5955 0.244507H105.176C135.676 0.244507 151.875 16.6535 151.875 46.6407V103.848C151.875 133.835 135.676 150.245 105.176 150.245Z" fill="white"/>
<path d="M97.8937 102.44C95.0688 102.44 92.5517 101.569 90.4679 99.8627C87.9787 97.8544 85.9999 94.8592 85.9302 91.3391C85.8949 89.5606 87.776 89.5606 87.776 89.5606C87.776 89.5606 90.8315 89.5249 92.3137 89.5249C93.7963 89.5606 94.2296 91.6845 94.2994 92.2172C94.873 96.9904 97.4948 99.071 99.5088 100.021C100.718 100.59 100.418 102.368 97.8937 102.447V102.44Z" fill="#263238"/>
<path d="M72.5271 78.1857C70.0379 77.8837 66.3459 77.6243 63.9617 77.0198C60.0949 76.0407 60.1999 72.4626 60.3678 71.2963C60.8364 67.7976 62.081 64.558 63.9966 61.5487C66.3808 57.8628 69.8072 54.6232 74.212 51.988C82.4766 47.0567 94.0347 44.3068 106.732 44.3068C116.038 44.3068 125.751 45.5522 125.848 45.5522C126.723 45.6674 127.359 46.5313 127.324 47.5103C127.289 48.4894 126.618 49.2382 125.743 49.3172C124.331 49.2811 122.954 49.2811 121.646 49.2811C108.711 49.2811 99.5024 51.0884 92.6502 55.0047C85.8958 58.8418 81.1902 64.7161 78.0017 73.4129C77.666 74.1686 76.5544 78.6467 72.5271 78.193V78.1857Z" fill="#263238"/>
<path d="M83.8115 114.074C77.3927 114.211 71.3445 111.223 66.7646 106.011C62.8841 101.598 60.549 97.1485 60.1152 91.4396C59.8496 87.7107 61.451 86.4219 63.6252 86.6453C65.1288 86.7961 69.8064 87.0268 72.4841 87.5956C74.4909 88.0131 75.8267 89.1576 76.1619 91.5118C77.9311 104.067 85.6225 109.006 90.2023 110.071C91.0063 110.259 91.5029 110.791 91.4749 111.749C91.4396 112.663 90.9292 113.916 83.8184 114.067L83.8115 114.074Z" fill="#263238"/>
<path d="M65.5266 125.219C60.9747 125.413 56.5209 124.47 55.2483 124.24C49.8925 123.261 45.4035 121.569 41.5578 119.057C32.3492 113.053 26.7906 102.448 26.1263 89.8926C25.9585 86.9125 25.5879 81.2178 32.4891 81.6499C35.3348 81.808 39.8517 83.2192 43.033 84.1622C46.9837 85.2998 48.8924 88.4384 48.8924 91.5414C48.8924 109.244 64.464 120.943 71.2602 120.943C72.8822 120.943 73.267 121.936 73.0712 122.887C72.9171 123.643 72.5048 124.924 65.5335 125.226L65.5266 125.219Z" fill="#263238"/>
<path d="M41.7743 73.196C39.0822 72.6994 36.4532 71.5549 33.8941 70.8998C25.9791 68.8768 27.3286 60.6552 28.0697 58.402C35.2716 36.574 58.1356 29.2741 76.5877 27.3231C93.9629 25.4873 112.143 26.9056 129.077 31.7578C130.287 32.089 133.622 32.9169 132.902 34.9326C132.182 36.9484 130.175 35.5446 111.702 35.127C105.612 34.9902 99.5287 35.3862 93.4245 36.3509C82.9505 37.9923 72.1825 41.3976 63.5263 48.5822C59.3523 52.0594 55.6813 56.4941 53.2274 61.7352C52.584 63.1103 52.0805 64.4851 51.6471 65.8602C51.2064 67.3144 49.5564 74.6145 41.7812 73.2033L41.7743 73.196Z" fill="#263238"/>
<path d="M86.0697 77.4094C87.5518 70.5771 94.1384 59.7495 114.527 60.4983C119.205 60.6495 117.065 63.9325 114.758 63.8603C103.228 63.4501 97.7603 71.1029 94.8933 78.8636C93.9706 81.3617 91.8657 81.7359 89.2298 81.2895C87.384 80.951 85.3772 80.728 86.0697 77.4094Z" fill="#263238"/>
</g>
<defs>
<clipPath id="clip0_450_96">
<rect width="729.73" height="150" fill="white" transform="translate(0.896423 0.244507)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 144 KiB

View File

@ -0,0 +1,16 @@
<svg width="708" height="161" viewBox="0 0 708 161" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_449_60)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M130.507 80.4344C130.507 90.457 127.596 100.065 122.612 108L142.992 128.473C152.974 115.112 159.21 98.4055 159.21 80.4478C159.21 62.4901 152.974 45.7903 142.992 32.4224L122.612 52.8957C127.832 61.1375 130.575 70.7054 130.513 80.4612L130.507 80.4344Z" fill="#B8D3F4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M79.7982 131.377C51.9384 131.377 29.0631 108.408 29.0631 80.4277C29.0631 52.4475 51.9317 29.4785 79.7982 29.4785C90.1955 29.4785 99.3349 32.4023 107.243 37.8284L127.623 17.3551C114.309 7.33251 97.6823 1.07007 79.7982 1.07007C36.1351 1.07007 0.380371 36.5706 0.380371 80.8359C0.380371 125.101 36.1418 160.187 79.7982 160.187C98.0971 160.187 114.723 153.924 127.971 143.902L107.591 123.428C99.6962 128.446 90.1955 131.364 79.7313 131.364L79.7982 131.377Z" fill="#3385FF"/>
<path d="M686.692 93.6351C684.862 93.6496 683.049 93.2727 681.376 92.5296C679.702 91.7865 678.208 90.6943 676.991 89.3263C676.857 89.0587 676.857 88.791 677.125 88.6572L699.759 79.0896L706.363 76.2595C706.63 76.1257 706.63 75.9918 706.63 75.858C705.568 71.2422 702.928 67.1418 699.166 64.2642C695.404 61.3865 690.756 59.9119 686.023 60.0949C675.519 60.4963 666.754 68.9934 666.353 79.4977C666.216 82.2664 666.646 85.0339 667.614 87.6312C668.583 90.2286 670.07 92.6014 671.986 94.6048C673.902 96.6083 676.206 98.2005 678.757 99.2841C681.309 100.368 684.054 100.92 686.826 100.908C695.31 100.908 702.589 95.6556 705.694 88.2491C705.828 87.9815 705.694 87.7138 705.426 87.7138L698.822 86.5095C698.689 86.5095 698.421 86.5095 698.421 86.7771C695.999 90.8183 691.683 93.6484 686.699 93.6484L686.692 93.6351ZM686.692 67.5014C690.6 67.5014 694.099 69.2544 696.394 71.944C696.527 72.2116 696.527 72.4793 696.26 72.6131L674.167 81.98C674.115 82.0019 674.058 82.0123 674 82.0104C673.943 82.0086 673.887 81.9945 673.836 81.9692C673.784 81.9438 673.739 81.9078 673.703 81.8635C673.666 81.8192 673.64 81.7677 673.625 81.7123V80.6351C673.492 73.2286 679.42 67.4345 686.692 67.4345V67.5014ZM665.403 60.764H658.939C658.806 60.764 658.672 60.8978 658.538 61.0316L648.034 91.8085C647.766 92.4776 646.963 92.4776 646.696 91.8085L636.191 61.0316C636.191 60.8978 636.058 60.764 635.79 60.764H629.327C629.059 60.764 628.925 61.0316 628.925 61.2992L642.26 100.092C642.26 100.225 642.394 100.359 642.661 100.359H651.827C651.961 100.359 652.095 100.225 652.229 100.092L665.563 61.2992C665.831 61.0316 665.697 60.764 665.429 60.764H665.403ZM622.85 60.764H616.247C615.979 60.764 615.845 60.8978 615.845 61.1654V99.9577C615.845 100.225 615.979 100.359 616.247 100.359H622.85C623.118 100.359 623.252 100.225 623.252 99.9577V61.2323C623.252 61.0985 623.118 60.8309 622.85 60.8309V60.764ZM622.85 48.1053H616.247C615.979 48.1053 615.845 48.2391 615.845 48.5067V55.1104C615.845 55.378 615.979 55.5118 616.247 55.5118H622.85C623.118 55.5118 623.252 55.378 623.252 55.1104V48.5067C623.252 48.2391 623.118 48.1053 622.85 48.1053ZM609.235 60.764H601.828C601.694 60.764 601.561 60.6301 601.561 60.4963V43.656C601.561 43.3884 601.427 43.2545 601.159 43.2545H594.616C594.348 43.2545 594.214 43.3884 594.214 43.656V60.5632C594.214 60.697 594.08 60.8309 593.947 60.8309H586.674C586.406 60.8309 586.272 60.9647 586.272 61.2323V67.8359C586.272 68.1036 586.406 68.2374 586.674 68.2374H593.947C594.08 68.2374 594.214 68.3712 594.214 68.505V99.951C594.214 100.219 594.348 100.352 594.616 100.352H601.219C601.487 100.352 601.621 100.219 601.621 99.951V68.505C601.621 68.3712 601.755 68.2374 601.888 68.2374H609.295C609.563 68.2374 609.696 68.1036 609.696 67.8359V61.2323C609.696 61.0985 609.429 60.8309 609.295 60.8309L609.235 60.764Z" fill="#0A0A0A"/>
<path d="M565.993 93.6351C564.057 93.6322 562.145 93.2029 560.394 92.3775C558.642 91.5521 557.094 90.351 555.86 88.8595C554.625 87.3681 553.734 85.6229 553.25 83.7482C552.766 81.8734 552.702 79.9151 553.06 78.0124C553.863 73.4293 558.446 68.7124 563.096 67.7757C565.376 67.2523 567.754 67.3512 569.982 68.0621C572.21 68.7731 574.207 70.0699 575.762 71.8169C575.895 71.9507 576.163 71.9507 576.297 71.8169L581.014 67.1C581.148 66.9662 581.148 66.6986 581.014 66.5647C577.106 62.256 571.312 59.6935 564.983 59.9611C554.612 60.4963 545.982 68.9934 545.446 79.3639C544.911 91.0859 554.211 100.774 565.92 100.774C571.848 100.774 577.227 98.2115 581.007 94.1703C581.141 94.0365 581.141 93.9027 581.007 93.7689L576.29 89.052C576.156 88.9182 575.889 88.9182 575.755 89.052C573.467 92.0159 569.961 93.6351 566.053 93.6351H565.993Z" fill="#0A0A0A"/>
<path d="M521.942 93.6351C520.112 93.6496 518.299 93.2727 516.626 92.5296C514.953 91.7865 513.458 90.6943 512.241 89.3263C512.107 89.0586 512.107 88.791 512.375 88.6572L535.009 79.0896L541.613 76.2595C541.88 76.1257 541.88 75.9918 541.88 75.858C540.818 71.2422 538.179 67.1418 534.417 64.2642C530.655 61.3865 526.006 59.9119 521.273 60.0949C510.769 60.4963 502.004 68.9934 501.603 79.4977C501.466 82.2664 501.896 85.0339 502.864 87.6312C503.833 90.2286 505.32 92.6014 507.236 94.6048C509.152 96.6083 511.456 98.2004 514.007 99.2841C516.559 100.368 519.304 100.92 522.076 100.908C530.56 100.908 537.839 95.6556 540.944 88.2491C541.078 87.9815 540.944 87.7138 540.676 87.7138L534.072 86.5095C533.939 86.5095 533.671 86.5095 533.671 86.7771C531.249 90.8183 526.8 93.6484 521.949 93.6484L521.942 93.6351ZM521.942 67.5014C525.85 67.5014 529.349 69.2544 531.644 71.944C531.778 72.2116 531.778 72.4793 531.51 72.6131L509.417 81.98C509.365 82.0019 509.308 82.0123 509.25 82.0104C509.193 82.0086 509.137 81.9945 509.086 81.9692C509.034 81.9438 508.989 81.9078 508.953 81.8635C508.917 81.8192 508.89 81.7676 508.876 81.7123V80.6351C508.742 73.2286 514.67 67.4345 521.942 67.4345V67.5014ZM495.942 43.2545H489.339C489.071 43.2545 488.937 43.3884 488.937 43.656V99.9644C488.937 100.232 489.071 100.366 489.339 100.366H495.942C496.21 100.366 496.344 100.232 496.344 99.9644V43.656C496.344 43.3884 496.076 43.2545 495.942 43.2545ZM481.524 43.2545H474.854C474.586 43.2545 474.452 43.3884 474.452 43.656V99.9644C474.452 100.232 474.586 100.366 474.854 100.366H481.457C481.725 100.366 481.859 100.232 481.859 99.9644V43.656C481.725 43.3884 481.591 43.2545 481.457 43.2545H481.524ZM449.195 67.5014C456.468 67.5014 462.262 73.4293 462.262 80.5682C462.232 84.0246 460.846 87.3309 458.402 89.775C455.958 92.2191 452.651 93.6054 449.195 93.6351C445.739 93.6054 442.432 92.2191 439.988 89.775C437.544 87.3309 436.158 84.0246 436.128 80.5682C436.128 73.4293 441.922 67.5014 449.195 67.5014ZM449.195 60.0949C446.506 60.0949 443.844 60.6244 441.36 61.6533C438.876 62.6822 436.619 64.1903 434.718 66.0914C432.817 67.9925 431.309 70.2495 430.28 72.7334C429.251 75.2174 428.722 77.8796 428.722 80.5682C428.722 83.2568 429.251 85.9191 430.28 88.403C431.309 90.887 432.817 93.144 434.718 95.0451C436.619 96.9462 438.876 98.4543 441.36 99.4831C443.844 100.512 446.506 101.042 449.195 101.042C451.884 101.042 454.546 100.512 457.03 99.4831C459.514 98.4543 461.771 96.9462 463.672 95.0451C465.573 93.144 467.081 90.887 468.11 88.403C469.139 85.9191 469.668 83.2568 469.668 80.5682C469.668 77.8796 469.139 75.2174 468.11 72.7334C467.081 70.2495 465.573 67.9925 463.672 66.0914C461.771 64.1903 459.514 62.6822 457.03 61.6533C454.546 60.6244 451.884 60.0949 449.195 60.0949Z" fill="#0A0A0A"/>
<path d="M411.607 93.6351C409.671 93.6322 407.759 93.2029 406.008 92.3775C404.256 91.5521 402.708 90.351 401.473 88.8595C400.239 87.3681 399.348 85.6229 398.864 83.7482C398.38 81.8734 398.315 79.9151 398.674 78.0124C399.477 73.4293 404.06 68.7124 408.71 67.7757C410.99 67.2523 413.368 67.3512 415.596 68.0621C417.824 68.7731 419.82 70.0699 421.375 71.8169C421.509 71.9507 421.777 71.9507 421.911 71.8169L426.628 67.1C426.761 66.9662 426.761 66.6986 426.628 66.5647C422.72 62.256 416.926 59.6935 410.597 59.9611C400.226 60.4963 391.595 68.9934 391.06 79.3639C390.525 91.0859 399.825 100.774 411.533 100.774C417.461 100.774 422.841 98.2115 426.621 94.1703C426.755 94.0365 426.755 93.9027 426.621 93.7689L421.904 89.052C421.77 88.9182 421.502 88.9182 421.369 89.052C419.08 92.0159 415.575 93.6351 411.667 93.6351H411.607Z" fill="#0A0A0A"/>
<path d="M344.654 60.0949C342.409 60.1896 340.211 60.7684 338.211 61.7915C336.211 62.8147 334.456 64.258 333.066 66.0228C332.932 66.2904 332.397 66.1566 332.397 65.889V61.1721C332.397 61.0383 332.263 60.7706 331.995 60.7706H325.391C325.258 60.7706 324.99 60.9044 324.99 61.1721V100.098C324.99 100.232 325.124 100.5 325.391 100.5H331.995C332.129 100.5 332.397 100.366 332.397 100.098V80.6351C332.397 73.2286 337.381 67.9765 344.52 67.9765C353.887 67.9765 356.777 74.172 356.777 81.4447V100.045C356.777 100.179 356.911 100.446 357.179 100.446H363.782C363.916 100.446 364.184 100.312 364.184 100.045V81.5116C364.318 70.1375 358.932 60.1016 344.647 60.1016L344.654 60.0949ZM301.145 93.6351C299.314 93.6496 297.501 93.2727 295.828 92.5296C294.155 91.7865 292.66 90.6943 291.443 89.3263C291.309 89.0587 291.309 88.791 291.577 88.6572L314.211 79.0896L320.815 76.2595C321.083 76.1257 321.083 75.9918 321.083 75.858C320.02 71.2422 317.381 67.1418 313.619 64.2642C309.857 61.3865 305.208 59.9119 300.475 60.0949C289.971 60.4963 281.206 68.9934 280.805 79.4977C280.669 82.2664 281.098 85.0339 282.066 87.6312C283.035 90.2286 284.522 92.6014 286.438 94.6048C288.354 96.6083 290.658 98.2005 293.209 99.2841C295.761 100.368 298.506 100.92 301.278 100.908C309.762 100.908 317.041 95.6556 320.146 88.2491C320.28 87.9815 320.146 87.7138 319.878 87.7138L313.275 86.5095C313.141 86.5095 312.873 86.5095 312.873 86.7771C310.451 90.8183 306.136 93.6484 301.151 93.6484L301.145 93.6351ZM301.145 67.5014C305.052 67.5014 308.551 69.2544 310.846 71.944C310.98 72.2116 310.98 72.4793 310.712 72.6131L288.62 81.98C288.567 82.0019 288.51 82.0123 288.453 82.0105C288.395 82.0086 288.339 81.9945 288.288 81.9692C288.237 81.9438 288.191 81.9078 288.155 81.8635C288.119 81.8192 288.092 81.7677 288.078 81.7123V80.6351C288.066 78.9097 288.395 77.1989 289.046 75.6009C289.697 74.0029 290.657 72.549 291.871 71.3227C293.085 70.0964 294.529 69.1217 296.12 68.4544C297.712 67.7872 299.419 67.4406 301.145 67.4345V67.5014ZM256.418 59.6935C251.299 59.8273 247.251 61.8478 244.428 65.2132C244.16 65.4808 243.759 65.347 243.759 64.9456V61.1721C243.759 60.9044 243.625 60.7706 243.357 60.7706H236.754C236.486 60.7706 236.352 60.9044 236.352 61.1721V117.614C236.352 117.882 236.486 118.016 236.754 118.016H243.217C243.485 118.016 243.618 117.882 243.618 117.614V95.388C243.618 94.9866 244.02 94.8528 244.287 95.1204C247.118 98.4858 251.293 100.64 256.679 100.64C269.21 100.64 279.172 89.2661 276.75 76.3933C275.131 66.558 266.253 59.5529 256.418 59.6935ZM258.98 93.0998C256.922 93.4421 254.812 93.2883 252.825 92.6512C250.838 92.0141 249.031 90.912 247.556 89.4366C246.081 87.9611 244.978 86.1549 244.341 84.168C243.704 82.181 243.55 80.0707 243.893 78.0124C244.829 72.6264 249.145 68.1772 254.598 67.3074C256.656 66.9651 258.766 67.1189 260.753 67.756C262.74 68.3931 264.547 69.4952 266.022 70.9706C267.497 72.4461 268.599 74.2523 269.237 76.2392C269.874 78.2262 270.027 80.3365 269.685 82.3948C268.882 87.7807 264.433 92.0962 258.98 93.0998ZM212.099 60.0949C209.41 60.0949 206.748 60.6244 204.264 61.6533C201.78 62.6822 199.523 64.1903 197.622 66.0914C195.721 67.9925 194.213 70.2495 193.184 72.7334C192.155 75.2174 191.625 77.8796 191.625 80.5682C191.625 83.2568 192.155 85.9191 193.184 88.403C194.213 90.887 195.721 93.144 197.622 95.0451C199.523 96.9462 201.78 98.4543 204.264 99.4831C206.748 100.512 209.41 101.042 212.099 101.042C217.529 101.042 222.736 98.8846 226.576 95.0451C230.415 91.2056 232.572 85.9981 232.572 80.5682C232.572 75.1384 230.415 69.9309 226.576 66.0914C222.736 62.2519 217.529 60.0949 212.099 60.0949ZM212.099 67.5014C219.372 67.5014 225.166 73.4293 225.166 80.5682C225.136 84.0246 223.75 87.331 221.306 89.775C218.862 92.2191 215.555 93.6054 212.099 93.6351C208.643 93.6054 205.336 92.2191 202.892 89.775C200.448 87.331 199.062 84.0246 199.032 80.5682C199.062 77.1119 200.448 73.8055 202.892 71.3614C205.336 68.9173 208.643 67.5311 212.099 67.5014Z" fill="#0A0A0A"/>
</g>
<defs>
<clipPath id="clip0_449_60">
<rect width="707" height="160" fill="white" transform="translate(0.129395 0.635193)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,16 @@
<svg width="708" height="160" viewBox="0 0 708 160" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_449_49)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M130.507 79.8251C130.507 89.8476 127.596 99.4554 122.612 107.39L142.992 127.864C152.974 114.503 159.21 97.7961 159.21 79.8384C159.21 61.8808 152.974 45.1809 142.992 31.813L122.612 52.2864C127.832 60.5281 130.575 70.0961 130.513 79.8518L130.507 79.8251Z" fill="#B8D3F4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M79.7982 130.768C51.9384 130.768 29.0631 107.799 29.0631 79.8184C29.0631 51.8381 51.9317 28.8692 79.7982 28.8692C90.1955 28.8692 99.3349 31.793 107.243 37.2191L127.623 16.7457C114.309 6.72315 97.6823 0.460711 79.7982 0.460711C36.1351 0.460711 0.380371 35.9612 0.380371 80.2265C0.380371 124.492 36.1418 159.577 79.7982 159.577C98.0971 159.577 114.723 153.315 127.971 143.292L107.591 122.819C99.6962 127.837 90.1955 130.754 79.7313 130.754L79.7982 130.768Z" fill="#3385FF"/>
<path d="M686.692 93.0257C684.862 93.0403 683.049 92.6634 681.376 91.9203C679.702 91.1771 678.208 90.085 676.991 88.7169C676.857 88.4493 676.857 88.1817 677.125 88.0479L699.759 78.4802L706.363 75.6501C706.63 75.5163 706.63 75.3825 706.63 75.2487C705.568 70.6329 702.928 66.5325 699.166 63.6548C695.404 60.7771 690.756 59.3026 686.023 59.4855C675.519 59.887 666.754 68.3841 666.353 78.8884C666.216 81.6571 666.646 84.4245 667.614 87.0219C668.583 89.6192 670.07 91.992 671.986 93.9955C673.902 95.999 676.206 97.5911 678.757 98.6748C681.309 99.7584 684.054 100.311 686.826 100.298C695.31 100.298 702.589 95.0463 705.694 87.6397C705.828 87.3721 705.694 87.1045 705.426 87.1045L698.822 85.9002C698.689 85.9002 698.421 85.9002 698.421 86.1678C695.999 90.2089 691.683 93.0391 686.699 93.0391L686.692 93.0257ZM686.692 66.8921C690.6 66.8921 694.099 68.645 696.394 71.3346C696.527 71.6023 696.527 71.8699 696.26 72.0037L674.167 81.3706C674.115 81.3926 674.058 81.4029 674 81.4011C673.943 81.3992 673.887 81.3852 673.836 81.3598C673.784 81.3345 673.739 81.2984 673.703 81.2541C673.666 81.2098 673.64 81.1583 673.625 81.103V80.0258C673.492 72.6192 679.42 66.8252 686.692 66.8252V66.8921ZM665.403 60.1546H658.939C658.806 60.1546 658.672 60.2884 658.538 60.4222L648.034 91.1991C647.766 91.8682 646.963 91.8682 646.696 91.1991L636.191 60.4222C636.191 60.2884 636.058 60.1546 635.79 60.1546H629.327C629.059 60.1546 628.925 60.4222 628.925 60.6898L642.26 99.4822C642.26 99.616 642.394 99.7498 642.661 99.7498H651.827C651.961 99.7498 652.095 99.616 652.229 99.4822L665.563 60.6898C665.831 60.4222 665.697 60.1546 665.429 60.1546H665.403ZM622.85 60.1546H616.247C615.979 60.1546 615.845 60.2884 615.845 60.556V99.3483C615.845 99.616 615.979 99.7498 616.247 99.7498H622.85C623.118 99.7498 623.252 99.616 623.252 99.3483V60.6229C623.252 60.4891 623.118 60.2215 622.85 60.2215V60.1546ZM622.85 47.4959H616.247C615.979 47.4959 615.845 47.6297 615.845 47.8973V54.501C615.845 54.7686 615.979 54.9024 616.247 54.9024H622.85C623.118 54.9024 623.252 54.7686 623.252 54.501V47.8973C623.252 47.6297 623.118 47.4959 622.85 47.4959ZM609.235 60.1546H601.828C601.694 60.1546 601.561 60.0208 601.561 59.887V43.0466C601.561 42.779 601.427 42.6452 601.159 42.6452H594.616C594.348 42.6452 594.214 42.779 594.214 43.0466V59.9539C594.214 60.0877 594.08 60.2215 593.947 60.2215H586.674C586.406 60.2215 586.272 60.3553 586.272 60.6229V67.2266C586.272 67.4942 586.406 67.628 586.674 67.628H593.947C594.08 67.628 594.214 67.7618 594.214 67.8957V99.3417C594.214 99.6093 594.348 99.7431 594.616 99.7431H601.219C601.487 99.7431 601.621 99.6093 601.621 99.3417V67.8957C601.621 67.7618 601.755 67.628 601.888 67.628H609.295C609.563 67.628 609.696 67.4942 609.696 67.2266V60.6229C609.696 60.4891 609.429 60.2215 609.295 60.2215L609.235 60.1546Z" fill="white"/>
<path d="M565.993 93.0257C564.057 93.0229 562.145 92.5935 560.394 91.7681C558.642 90.9427 557.094 89.7416 555.86 88.2502C554.625 86.7587 553.734 85.0136 553.25 83.1388C552.766 81.2641 552.702 79.3057 553.06 77.4031C553.863 72.82 558.446 68.1031 563.096 67.1664C565.376 66.6429 567.754 66.7418 569.982 67.4528C572.21 68.1637 574.207 69.4605 575.762 71.2075C575.895 71.3413 576.163 71.3413 576.297 71.2075L581.014 66.4906C581.148 66.3568 581.148 66.0892 581.014 65.9554C577.106 61.6466 571.312 59.0841 564.983 59.3517C554.612 59.887 545.982 68.3841 545.446 78.7546C544.911 90.4766 554.211 100.165 565.92 100.165C571.848 100.165 577.227 97.6021 581.007 93.561C581.141 93.4271 581.141 93.2933 581.007 93.1595L576.29 88.4426C576.156 88.3088 575.889 88.3088 575.755 88.4426C573.467 91.4066 569.961 93.0257 566.053 93.0257H565.993Z" fill="white"/>
<path d="M521.942 93.0257C520.112 93.0403 518.299 92.6634 516.626 91.9203C514.953 91.1771 513.458 90.085 512.241 88.7169C512.107 88.4493 512.107 88.1817 512.375 88.0478L535.009 78.4802L541.613 75.6501C541.88 75.5163 541.88 75.3825 541.88 75.2487C540.818 70.6329 538.179 66.5325 534.417 63.6548C530.655 60.7771 526.006 59.3026 521.273 59.4855C510.769 59.887 502.004 68.3841 501.603 78.8884C501.466 81.6571 501.896 84.4245 502.864 87.0219C503.833 89.6192 505.32 91.992 507.236 93.9955C509.152 95.999 511.456 97.5911 514.007 98.6747C516.559 99.7584 519.304 100.311 522.076 100.298C530.56 100.298 537.839 95.0463 540.944 87.6397C541.078 87.3721 540.944 87.1045 540.676 87.1045L534.072 85.9002C533.939 85.9002 533.671 85.9002 533.671 86.1678C531.249 90.2089 526.8 93.0391 521.949 93.0391L521.942 93.0257ZM521.942 66.8921C525.85 66.8921 529.349 68.645 531.644 71.3346C531.778 71.6023 531.778 71.8699 531.51 72.0037L509.417 81.3706C509.365 81.3926 509.308 81.4029 509.25 81.4011C509.193 81.3992 509.137 81.3851 509.086 81.3598C509.034 81.3345 508.989 81.2984 508.953 81.2541C508.917 81.2098 508.89 81.1583 508.876 81.103V80.0258C508.742 72.6192 514.67 66.8252 521.942 66.8252V66.8921ZM495.942 42.6452H489.339C489.071 42.6452 488.937 42.779 488.937 43.0466V99.355C488.937 99.6227 489.071 99.7565 489.339 99.7565H495.942C496.21 99.7565 496.344 99.6227 496.344 99.355V43.0466C496.344 42.779 496.076 42.6452 495.942 42.6452ZM481.524 42.6452H474.854C474.586 42.6452 474.452 42.779 474.452 43.0466V99.355C474.452 99.6227 474.586 99.7565 474.854 99.7565H481.457C481.725 99.7565 481.859 99.6227 481.859 99.355V43.0466C481.725 42.779 481.591 42.6452 481.457 42.6452H481.524ZM449.195 66.8921C456.468 66.8921 462.262 72.82 462.262 79.9589C462.232 83.4152 460.846 86.7216 458.402 89.1657C455.958 91.6098 452.651 92.996 449.195 93.0257C445.739 92.996 442.432 91.6098 439.988 89.1657C437.544 86.7216 436.158 83.4152 436.128 79.9589C436.128 72.82 441.922 66.8921 449.195 66.8921ZM449.195 59.4855C446.506 59.4855 443.844 60.0151 441.36 61.044C438.876 62.0729 436.619 63.5809 434.718 65.482C432.817 67.3832 431.309 69.6401 430.28 72.1241C429.251 74.608 428.722 77.2703 428.722 79.9589C428.722 82.6475 429.251 85.3097 430.28 87.7937C431.309 90.2776 432.817 92.5346 434.718 94.4357C436.619 96.3368 438.876 97.8449 441.36 98.8738C443.844 99.9027 446.506 100.432 449.195 100.432C451.884 100.432 454.546 99.9027 457.03 98.8738C459.514 97.8449 461.771 96.3368 463.672 94.4357C465.573 92.5346 467.081 90.2776 468.11 87.7937C469.139 85.3097 469.668 82.6475 469.668 79.9589C469.668 77.2703 469.139 74.608 468.11 72.1241C467.081 69.6401 465.573 67.3832 463.672 65.482C461.771 63.5809 459.514 62.0729 457.03 61.044C454.546 60.0151 451.884 59.4855 449.195 59.4855Z" fill="white"/>
<path d="M411.607 93.0257C409.671 93.0229 407.759 92.5935 406.008 91.7681C404.256 90.9427 402.708 89.7416 401.473 88.2502C400.239 86.7587 399.348 85.0136 398.864 83.1388C398.38 81.2641 398.315 79.3057 398.674 77.4031C399.477 72.82 404.06 68.1031 408.71 67.1664C410.99 66.6429 413.368 66.7418 415.596 67.4528C417.824 68.1637 419.82 69.4605 421.375 71.2075C421.509 71.3413 421.777 71.3413 421.911 71.2075L426.628 66.4906C426.761 66.3568 426.761 66.0892 426.628 65.9554C422.72 61.6466 416.926 59.0841 410.597 59.3517C400.226 59.887 391.595 68.3841 391.06 78.7546C390.525 90.4766 399.825 100.165 411.533 100.165C417.461 100.165 422.841 97.6021 426.621 93.561C426.755 93.4271 426.755 93.2933 426.621 93.1595L421.904 88.4426C421.77 88.3088 421.502 88.3088 421.369 88.4426C419.08 91.4066 415.575 93.0257 411.667 93.0257H411.607Z" fill="white"/>
<path d="M344.654 59.4855C342.409 59.5802 340.211 60.159 338.211 61.1822C336.211 62.2053 334.456 63.6487 333.066 65.4134C332.932 65.6811 332.397 65.5472 332.397 65.2796V60.5627C332.397 60.4289 332.263 60.1613 331.995 60.1613H325.391C325.258 60.1613 324.99 60.2951 324.99 60.5627V99.4889C324.99 99.6227 325.124 99.8903 325.391 99.8903H331.995C332.129 99.8903 332.397 99.7565 332.397 99.4889V80.0258C332.397 72.6192 337.381 67.3671 344.52 67.3671C353.887 67.3671 356.777 73.5626 356.777 80.8354V99.4353C356.777 99.5691 356.911 99.8368 357.179 99.8368H363.782C363.916 99.8368 364.184 99.703 364.184 99.4353V80.9023C364.318 69.5282 358.932 59.4922 344.647 59.4922L344.654 59.4855ZM301.145 93.0257C299.314 93.0403 297.501 92.6634 295.828 91.9203C294.155 91.1771 292.66 90.085 291.443 88.7169C291.309 88.4493 291.309 88.1817 291.577 88.0479L314.211 78.4802L320.815 75.6501C321.083 75.5163 321.083 75.3825 321.083 75.2487C320.02 70.6329 317.381 66.5325 313.619 63.6548C309.857 60.7771 305.208 59.3026 300.475 59.4855C289.971 59.887 281.206 68.3841 280.805 78.8884C280.669 81.6571 281.098 84.4245 282.066 87.0219C283.035 89.6192 284.522 91.992 286.438 93.9955C288.354 95.999 290.658 97.5911 293.209 98.6748C295.761 99.7584 298.506 100.311 301.278 100.298C309.762 100.298 317.041 95.0463 320.146 87.6397C320.28 87.3721 320.146 87.1045 319.878 87.1045L313.275 85.9002C313.141 85.9002 312.873 85.9002 312.873 86.1678C310.451 90.2089 306.136 93.0391 301.151 93.0391L301.145 93.0257ZM301.145 66.8921C305.052 66.8921 308.551 68.645 310.846 71.3346C310.98 71.6023 310.98 71.8699 310.712 72.0037L288.62 81.3706C288.567 81.3926 288.51 81.403 288.453 81.4011C288.395 81.3992 288.339 81.3852 288.288 81.3598C288.237 81.3345 288.191 81.2984 288.155 81.2541C288.119 81.2098 288.092 81.1583 288.078 81.103V80.0258C288.066 78.3003 288.395 76.5895 289.046 74.9915C289.697 73.3935 290.657 71.9397 291.871 70.7133C293.085 69.487 294.529 68.5123 296.12 67.8451C297.712 67.1779 299.419 66.8313 301.145 66.8252V66.8921ZM256.418 59.0841C251.299 59.2179 247.251 61.2385 244.428 64.6039C244.16 64.8715 243.759 64.7377 243.759 64.3362V60.5627C243.759 60.2951 243.625 60.1613 243.357 60.1613H236.754C236.486 60.1613 236.352 60.2951 236.352 60.5627V117.005C236.352 117.273 236.486 117.406 236.754 117.406H243.217C243.485 117.406 243.618 117.273 243.618 117.005V94.7787C243.618 94.3772 244.02 94.2434 244.287 94.511C247.118 97.8764 251.293 100.031 256.679 100.031C269.21 100.031 279.172 88.6567 276.75 75.7839C275.131 65.9487 266.253 58.9436 256.418 59.0841ZM258.98 92.4904C256.922 92.8328 254.812 92.679 252.825 92.0419C250.838 91.4047 249.031 90.3027 247.556 88.8272C246.081 87.3518 244.978 85.5456 244.341 83.5586C243.704 81.5717 243.55 79.4614 243.893 77.4031C244.829 72.0171 249.145 67.5678 254.598 66.698C256.656 66.3557 258.766 66.5095 260.753 67.1466C262.74 67.7837 264.547 68.8858 266.022 70.3612C267.497 71.8367 268.599 73.6429 269.237 75.6299C269.874 77.6168 270.027 79.7271 269.685 81.7854C268.882 87.1714 264.433 91.4869 258.98 92.4904ZM212.099 59.4855C209.41 59.4855 206.748 60.0151 204.264 61.044C201.78 62.0729 199.523 63.5809 197.622 65.482C195.721 67.3832 194.213 69.6401 193.184 72.1241C192.155 74.608 191.625 77.2703 191.625 79.9589C191.625 82.6475 192.155 85.3097 193.184 87.7937C194.213 90.2776 195.721 92.5346 197.622 94.4357C199.523 96.3369 201.78 97.8449 204.264 98.8738C206.748 99.9027 209.41 100.432 212.099 100.432C217.529 100.432 222.736 98.2752 226.576 94.4357C230.415 90.5962 232.572 85.3887 232.572 79.9589C232.572 74.529 230.415 69.3215 226.576 65.482C222.736 61.6425 217.529 59.4855 212.099 59.4855ZM212.099 66.8921C219.372 66.8921 225.166 72.82 225.166 79.9589C225.136 83.4152 223.75 86.7216 221.306 89.1657C218.862 91.6098 215.555 92.996 212.099 93.0257C208.643 92.996 205.336 91.6098 202.892 89.1657C200.448 86.7216 199.062 83.4152 199.032 79.9589C199.062 76.5025 200.448 73.1962 202.892 70.7521C205.336 68.308 208.643 66.9218 212.099 66.8921Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_449_49">
<rect width="707" height="160" fill="white" transform="translate(0.129395)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,16 @@
<svg width="530" height="141" viewBox="0 0 530 141" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_449_71)">
<path d="M168.99 28.0228C167.904 28.0228 166.98 28.8118 166.81 29.8861L154.23 109.667C154.185 109.958 154.202 110.256 154.283 110.54C154.363 110.824 154.504 111.087 154.696 111.311C154.888 111.535 155.126 111.715 155.394 111.838C155.661 111.962 155.953 112.026 156.248 112.026H171.167C172.252 112.026 173.177 111.235 173.347 110.162L177.042 86.7343C177.211 85.6615 178.136 84.8709 179.222 84.8709H192.786C208.635 84.8709 222.089 73.3073 224.546 57.6382C227.024 41.8364 214.679 28.0571 197.182 28.0228H168.99ZM183.493 45.8329H194.346C203.28 45.8329 206.19 51.1048 205.27 56.9349C204.35 62.7775 199.831 67.0811 191.188 67.0811H180.142L183.493 45.8329ZM254.889 51.256C251.122 51.2701 246.788 52.0419 241.921 54.069C230.758 58.7218 225.397 68.3379 223.121 75.3484C223.121 75.3484 215.875 96.7371 232.246 108.488C232.246 108.488 247.428 119.8 264.52 107.791L264.225 109.667C264.179 109.958 264.197 110.255 264.277 110.539C264.357 110.823 264.498 111.085 264.689 111.31C264.881 111.534 265.118 111.714 265.386 111.837C265.654 111.961 265.945 112.025 266.239 112.026H280.402C281.489 112.026 282.412 111.235 282.582 110.162L291.197 55.5284C291.243 55.237 291.226 54.9391 291.146 54.6551C291.066 54.3711 290.925 54.1078 290.734 53.8833C290.543 53.6589 290.305 53.4785 290.037 53.3547C289.769 53.2309 289.478 53.1666 289.183 53.1661H275.02C273.933 53.1661 273.008 53.9567 272.84 55.0295L272.377 57.9703C272.377 57.9703 266.193 51.2139 254.889 51.256ZM255.353 68.5126C256.979 68.5126 258.466 68.7371 259.801 69.1659C265.912 71.1275 269.377 76.9981 268.374 83.3616C267.138 91.1969 260.712 96.9663 252.471 96.9663C250.846 96.9663 249.359 96.7433 248.022 96.3145C241.913 94.3529 238.427 88.4822 239.429 82.1188C240.666 74.2834 247.112 68.5126 255.353 68.5126Z" fill="#003087"/>
<path d="M372.944 28.0228C371.857 28.0228 370.934 28.8118 370.764 29.8861L358.184 109.667C358.138 109.958 358.156 110.256 358.236 110.54C358.317 110.824 358.458 111.087 358.65 111.311C358.841 111.535 359.079 111.715 359.347 111.838C359.615 111.962 359.907 112.026 360.202 112.026H375.121C376.206 112.026 377.131 111.235 377.301 110.162L380.996 86.7343C381.165 85.6615 382.089 84.8709 383.176 84.8709H396.74C412.589 84.8709 426.041 73.3073 428.496 57.6382C430.976 41.8364 418.632 28.0571 401.136 28.0228H372.944ZM387.447 45.8329H398.299C407.234 45.8329 410.144 51.1048 409.224 56.9349C408.304 62.7775 403.787 67.0811 395.142 67.0811H384.096L387.447 45.8329ZM458.842 51.256C455.074 51.2701 450.74 52.0419 445.873 54.069C434.71 58.7218 429.349 68.3379 427.073 75.3484C427.073 75.3484 419.83 96.7371 436.201 108.488C436.201 108.488 451.38 119.8 468.473 107.791L468.178 109.667C468.133 109.958 468.151 110.256 468.231 110.54C468.311 110.824 468.452 111.087 468.644 111.311C468.836 111.535 469.074 111.715 469.342 111.838C469.61 111.962 469.901 112.026 470.196 112.026H484.358C485.444 112.026 486.368 111.235 486.537 110.162L495.156 55.5284C495.202 55.2368 495.184 54.9386 495.104 54.6544C495.024 54.3702 494.883 54.1067 494.691 53.8822C494.5 53.6576 494.261 53.4773 493.993 53.3537C493.725 53.2301 493.433 53.1661 493.138 53.1661H478.975C477.888 53.1661 476.963 53.9567 476.795 55.0295L476.332 57.9703C476.332 57.9703 470.145 51.2139 458.842 51.256ZM459.305 68.5126C460.931 68.5126 462.419 68.7371 463.753 69.1659C469.864 71.1275 473.329 76.9981 472.326 83.3616C471.09 91.1969 464.664 96.9663 456.423 96.9663C454.798 96.9663 453.311 96.7433 451.974 96.3145C445.865 94.3529 442.379 88.4822 443.381 82.1188C444.618 74.2834 451.064 68.5126 459.305 68.5126Z" fill="#0070E0"/>
<path d="M297.918 53.1661C296.795 53.1661 295.997 54.2623 296.34 55.3304L311.867 103.514L297.827 126.22C297.146 127.322 297.938 128.746 299.234 128.746H315.827C316.302 128.746 316.768 128.623 317.182 128.39C317.595 128.156 317.941 127.82 318.187 127.414L361.552 55.6766C362.218 54.5742 361.422 53.1646 360.134 53.1646H343.542C343.063 53.165 342.592 53.2901 342.176 53.5277C341.76 53.7653 341.413 54.1072 341.169 54.5196L324.099 83.3382L315.435 54.7332C315.152 53.8023 314.293 53.1646 313.322 53.1646L297.918 53.1661Z" fill="#003087"/>
<path d="M511.247 28.0228C510.162 28.0228 509.237 28.8133 509.068 29.8861L496.484 109.663C496.438 109.955 496.456 110.253 496.536 110.538C496.616 110.822 496.757 111.085 496.948 111.31C497.14 111.534 497.378 111.715 497.647 111.838C497.915 111.962 498.207 112.026 498.502 112.026H513.421C514.506 112.026 515.431 111.235 515.601 110.162L528.181 30.382C528.227 30.0908 528.209 29.7932 528.129 29.5096C528.049 29.226 527.908 28.9631 527.717 28.739C527.525 28.5148 527.288 28.3348 527.02 28.2112C526.752 28.0875 526.461 28.0233 526.167 28.0228H511.247Z" fill="#0070E0"/>
<path d="M44.6126 28.0228C43.6516 28.0236 42.7223 28.3671 41.9918 28.9916C41.2613 29.616 40.7773 30.4806 40.6271 31.4298L34.0049 73.4305C34.3136 71.469 36.0038 70.0235 37.9904 70.0235H57.3956C76.9255 70.0235 93.4975 55.7764 96.5256 36.4694C96.751 35.028 96.8787 33.573 96.9077 32.1143C91.9446 29.5119 86.1144 28.0228 79.726 28.0228H44.6126Z" fill="#001C64"/>
<path d="M96.9081 32.1159C96.879 33.5745 96.7513 35.0295 96.526 36.4709C93.4979 55.7779 76.9243 70.0251 57.3959 70.0251H37.9907C36.0057 70.0251 34.314 71.469 34.0052 73.4321L27.9162 112.027L24.1022 136.24C24.0278 136.708 24.0557 137.186 24.1841 137.642C24.3125 138.098 24.5383 138.521 24.8459 138.881C25.1535 139.241 25.5356 139.53 25.9658 139.728C26.396 139.927 26.8641 140.029 27.3377 140.029H48.4005C49.3615 140.028 50.2908 139.685 51.0213 139.06C51.7518 138.436 52.2357 137.571 52.3859 136.622L57.9338 101.434C58.0842 100.484 58.5686 99.6189 59.2997 98.9943C60.0309 98.3698 60.9609 98.0266 61.9225 98.0266H74.3235C93.8534 98.0266 110.425 83.7795 113.454 64.4725C115.604 50.7695 108.702 38.3 96.9081 32.1159Z" fill="#0070E0"/>
<path d="M20.684 0.0260696C18.699 0.0260696 17.0072 1.46996 16.6985 3.42998L0.170073 108.238C-0.143342 110.228 1.39415 112.027 3.40874 112.027H27.919L34.0049 73.4322L40.6271 31.4315C40.7774 30.4823 41.2613 29.6177 41.9918 28.9932C42.7224 28.3688 43.6516 28.0253 44.6127 28.0245H79.726C86.1159 28.0245 91.9446 29.5151 96.9078 32.116C97.2477 14.5383 82.7432 0.0260696 62.8016 0.0260696H20.684Z" fill="#003087"/>
</g>
<defs>
<clipPath id="clip0_449_71">
<rect width="529" height="141" fill="white" transform="translate(0.129395)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -1,4 +1,3 @@
import { t } from "@lingui/macro";
import { useTheme } from "@reactive-resume/hooks";
import { cn } from "@reactive-resume/utils";
@ -26,7 +25,7 @@ export const Icon = ({ size = 32, className }: Props) => {
src={src}
width={size}
height={size}
alt={t`Reactive Resume`}
alt="Reactive Resume"
className={cn("rounded-sm", className)}
/>
);

View File

@ -0,0 +1,79 @@
import { t } from "@lingui/macro";
import { useLingui } from "@lingui/react";
import { Check, Translate } from "@phosphor-icons/react";
import {
Button,
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
Popover,
PopoverContent,
PopoverTrigger,
ScrollArea,
} from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
import { useState } from "react";
import { changeLanguage } from "../providers/locale";
import { useLanguages } from "../services/resume/translation";
export const LocaleSwitch = () => {
const { i18n } = useLingui();
const { languages } = useLanguages();
const [open, setOpen] = useState(false);
const options = languages.map((language) => ({
label: language.name,
value: language.locale,
}));
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button size="icon" variant="ghost">
<Translate size={20} />
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="p-0">
<Command>
<CommandInput placeholder={t`Search for a language`} />
<CommandEmpty>{t`No results found`}</CommandEmpty>
<CommandGroup>
<ScrollArea orientation="vertical">
<div className="max-h-60">
{options.map((option) => (
<CommandItem
key={option.value}
value={option.value.toLowerCase().trim()}
onSelect={async (selectedValue) => {
const option = options.find(
(option) => option.value.toLowerCase().trim() === selectedValue,
);
if (!option) return null;
await changeLanguage(option.value);
setOpen(false);
}}
>
<Check
className={cn(
"mr-2 h-4 w-4 opacity-0",
i18n.locale === option.value && "opacity-100",
)}
/>
{option.label}{" "}
<span className="ml-1.5 text-xs opacity-50">({option.value})</span>
</CommandItem>
))}
</div>
</ScrollArea>
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
);
};

View File

@ -1,4 +1,3 @@
import { t } from "@lingui/macro";
import { useTheme } from "@reactive-resume/hooks";
import { cn } from "@reactive-resume/utils";
@ -26,7 +25,7 @@ export const Logo = ({ size = 32, className }: Props) => {
src={src}
width={size}
height={size}
alt={t`Reactive Resume`}
alt="Reactive Resume"
className={cn("rounded-sm", className)}
/>
);

View File

@ -1,30 +1,17 @@
import { i18n } from "@lingui/core";
import { t } from "@lingui/macro";
import { axios } from "./axios";
type Locale = "en-US" | "de-DE" | "zu-ZA";
export const defaultLocale = "en-US";
export const getLocales = () => {
const locales = {
"en-US": t`English`,
"de-DE": t`German`,
} as Record<Locale, string>;
if (process.env.NODE_ENV === "development") {
// eslint-disable-next-line lingui/no-unlocalized-strings
locales["zu-ZA"] = "Pseudolocalization";
}
return locales;
};
axios(`translation/${defaultLocale}`).then((response) => {
const messages = response.data;
i18n.loadAndActivate({ locale: defaultLocale, messages });
});
export async function dynamicActivate(locale: string) {
const response = await axios(`translation/${locale}`);
const messages = await response.data;
const messages = response.data;
i18n.load(locale, messages);
i18n.activate(locale);
i18n.loadAndActivate({ locale, messages });
}

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,9 @@ import { t } from "@lingui/macro";
import { useMemo } from "react";
import { Link, matchRoutes, Outlet, useLocation } from "react-router-dom";
import { LocaleSwitch } from "@/client/components/locale-switch";
import { Logo } from "@/client/components/logo";
import { ThemeSwitch } from "@/client/components/theme-switch";
import { SocialAuth } from "./_components/social-auth";
@ -15,10 +17,17 @@ export const AuthLayout = () => {
return (
<div className="flex h-screen w-screen">
<div className="flex w-full flex-col justify-center gap-y-8 px-12 sm:mx-auto sm:basis-[420px] sm:px-0 lg:basis-[480px] lg:px-12">
<Link to="/" className="h-24 w-24">
<Logo className="-ml-3" size={96} />
</Link>
<div className="relative flex w-full flex-col justify-center gap-y-8 px-12 sm:mx-auto sm:basis-[420px] sm:px-0 lg:basis-[480px] lg:px-12">
<div className="flex items-center justify-between">
<Link to="/" className="h-24 w-24">
<Logo className="-ml-3" size={96} />
</Link>
<div className="inset-x-0 bottom-0 space-x-2 text-right lg:absolute lg:p-12 lg:text-center">
<LocaleSwitch />
<ThemeSwitch />
</div>
</div>
<Outlet />
@ -45,7 +54,7 @@ export const AuthLayout = () => {
<img
width={1920}
height={1080}
alt={t`Open books on a table`}
alt="Open books on a table"
className="h-screen w-full object-cover object-center"
src="/backgrounds/patrick-tomasso-Oaqk7qqNh_c-unsplash.jpg"
/>

View File

@ -36,6 +36,7 @@ type Props = { id: SectionKey };
export const SectionOptions = ({ id }: Props) => {
const { open } = useDialog(id);
const setValue = useResumeStore((state) => state.setValue);
const removeSection = useResumeStore((state) => state.removeSection);

View File

@ -8,7 +8,6 @@ import { ThemeSwitch } from "@/client/components/theme-switch";
import { ExportSection } from "./sections/export";
import { InformationSection } from "./sections/information";
import { LayoutSection } from "./sections/layout";
import { LocaleSection } from "./sections/locale";
import { NotesSection } from "./sections/notes";
import { PageSection } from "./sections/page";
import { SharingSection } from "./sections/sharing";
@ -40,8 +39,6 @@ export const RightSidebar = () => {
<Separator />
<PageSection />
<Separator />
<LocaleSection />
<Separator />
<SharingSection />
<Separator />
<StatisticsSection />

View File

@ -26,7 +26,6 @@ const DonateCard = () => (
If you like the app and want to support keeping it free forever, please donate whatever
you can afford to give.
</p>
<p>Your donations could be tax-deductible, depending on your location.</p>
</Trans>
</CardDescription>
</CardContent>

View File

@ -1,48 +0,0 @@
import { t } from "@lingui/macro";
import { useLingui } from "@lingui/react";
import { Combobox, Label } from "@reactive-resume/ui";
import { useMemo } from "react";
import { dynamicActivate, getLocales } from "@/client/libs/lingui";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "../shared/section-icon";
export const LocaleSection = () => {
const { _ } = useLingui();
const setValue = useResumeStore((state) => state.setValue);
const locale = useResumeStore((state) => state.resume.data.metadata.locale);
const options = useMemo(() => {
return Object.entries(getLocales()).map(([value, label]) => ({
label,
value,
}));
}, [_]);
const onChangeLanguage = async (value: string) => {
setValue("metadata.locale", value);
await dynamicActivate(value);
// Update resume section titles with new locale
};
return (
<section id="locale" className="grid gap-y-6">
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("locale")}
<h2 className="line-clamp-1 text-3xl font-bold">{t`Locale`}</h2>
</div>
</header>
<main className="grid gap-y-4">
<div className="space-y-1.5">
<Label>{t`Language`}</Label>
<Combobox value={locale} onValueChange={onChangeLanguage} options={options} />
</div>
</main>
</section>
);
};

View File

@ -168,17 +168,26 @@ export const TypographySection = () => {
<div className="space-y-1.5">
<Label>{t`Options`}</Label>
<div className="py-2">
<div className="flex items-center gap-x-4">
<Switch
id="metadata.typography.underlineLinks"
checked={typography.underlineLinks}
onCheckedChange={(checked) => {
setValue("metadata.typography.underlineLinks", checked);
}}
/>
<Label htmlFor="metadata.typography.underlineLinks">{t`Underline Links`}</Label>
</div>
<div className="flex items-center gap-x-4 py-2">
<Switch
id="metadata.typography.hideIcons"
checked={typography.hideIcons}
onCheckedChange={(checked) => {
setValue("metadata.typography.hideIcons", checked);
}}
/>
<Label htmlFor="metadata.typography.hideIcons">{t`Hide Icons`}</Label>
</div>
<div className="flex items-center gap-x-4 py-2">
<Switch
id="metadata.typography.underlineLinks"
checked={typography.underlineLinks}
onCheckedChange={(checked) => {
setValue("metadata.typography.underlineLinks", checked);
}}
/>
<Label htmlFor="metadata.typography.underlineLinks">{t`Underline Links`}</Label>
</div>
</div>
</main>

View File

@ -85,10 +85,10 @@ export const ResumeCard = ({ resume }: Props) => {
layout
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
src={url}
loading="lazy"
alt={resume.title}
className="h-full w-full object-cover"
src={`${url}?cache=${new Date().getTime()}`}
/>
)}
</AnimatePresence>

View File

@ -129,10 +129,10 @@ export const ResumeListItem = ({ resume }: Props) => {
layout
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
src={url}
loading="lazy"
alt={resume.title}
className="aspect-[1/1.4142] w-60 rounded-sm object-cover"
src={`${url}?cache=${new Date().getTime()}`}
/>
)}
</AnimatePresence>

View File

@ -9,7 +9,7 @@ import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { dynamicActivate, getLocales } from "@/client/libs/lingui";
import { useLanguages } from "@/client/services/resume/translation";
import { useUpdateUser, useUser } from "@/client/services/user";
const formSchema = z.object({
@ -21,6 +21,7 @@ type FormValues = z.infer<typeof formSchema>;
export const ProfileSettings = () => {
const { user } = useUser();
const { languages } = useLanguages();
const { theme, setTheme } = useTheme();
const { updateUser, loading } = useUpdateUser();
@ -45,8 +46,10 @@ export const ProfileSettings = () => {
setTheme(data.theme);
if (user.locale !== data.locale) {
await dynamicActivate(data.locale);
window.localStorage.setItem("locale", data.locale);
await updateUser({ locale: data.locale });
window.location.reload();
}
form.reset(data);
@ -96,10 +99,7 @@ export const ProfileSettings = () => {
{...field}
value={field.value}
onValueChange={field.onChange}
options={Object.entries(getLocales()).map(([value, label]) => ({
label,
value,
}))}
options={languages.map(({ locale, name }) => ({ label: name, value: locale }))}
/>
</div>
<FormDescription>

View File

@ -2,6 +2,7 @@ import { t } from "@lingui/macro";
import { Separator } from "@reactive-resume/ui";
import { Copyright } from "@/client/components/copyright";
import { LocaleSwitch } from "@/client/components/locale-switch";
import { Logo } from "@/client/components/logo";
import { ThemeSwitch } from "@/client/components/theme-switch";
@ -23,7 +24,8 @@ export const Footer = () => (
</div>
<div className="relative col-start-4">
<div className="absolute bottom-0 right-0">
<div className="absolute bottom-0 right-0 space-x-2">
<LocaleSwitch />
<ThemeSwitch />
</div>
</div>

View File

@ -1,9 +1,15 @@
import { t } from "@lingui/macro";
import { Helmet } from "react-helmet-async";
import { ContributorsSection } from "./sections/contributors";
import { FAQSection } from "./sections/faq";
import { FeaturesSection } from "./sections/features";
import { HeroSection } from "./sections/hero";
import { LogoCloudSection } from "./sections/logo-cloud";
import { SampleResumesSection } from "./sections/sample-resumes";
import { StatisticsSection } from "./sections/statistics";
import { SupportSection } from "./sections/support";
import { TestimonialsSection } from "./sections/testimonials";
export const HomePage = () => (
<main className="relative isolate mb-[400px] overflow-hidden bg-background">
@ -16,5 +22,11 @@ export const HomePage = () => (
<HeroSection />
<LogoCloudSection />
<StatisticsSection />
<FeaturesSection />
<SampleResumesSection />
<TestimonialsSection />
<SupportSection />
<FAQSection />
<ContributorsSection />
</main>
);

View File

@ -0,0 +1,67 @@
import { t } from "@lingui/macro";
import { Avatar, AvatarFallback, AvatarImage, Tooltip } from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
import { motion } from "framer-motion";
import { useMemo } from "react";
import { useContributors } from "@/client/services/resume/contributors";
export const ContributorsSection = () => {
const { github, crowdin, loading } = useContributors();
const contributors = useMemo(() => {
if (github && crowdin) return [...github, ...crowdin];
return [];
}, [github, crowdin]);
return (
<section id="contributors" className="container relative space-y-12 py-24 sm:py-32">
<div className="space-y-6 text-center">
<h1 className="text-4xl font-bold">{t`By the community, for the community.`}</h1>
<p className="mx-auto max-w-3xl leading-loose">
{t`Reactive Resume thrives thanks to its vibrant community. This project owes its progress to numerous individuals who've dedicated their time and skills. Below, we celebrate the coders who've enhanced its features on GitHub and the linguists whose translations on Crowdin have made it accessible to a broader audience.`}
</p>
</div>
{loading && (
<div className="mx-auto flex max-w-5xl flex-wrap items-center justify-center gap-3">
{Array(30)
.fill(0)
.map((_, index) => (
<motion.div
key={index}
viewport={{ once: true }}
initial={{ opacity: 0, scale: 0 }}
whileInView={{ opacity: 1, scale: 1, transition: { delay: index * 0.05 } }}
>
<Avatar>
<AvatarFallback></AvatarFallback>
</Avatar>
</motion.div>
))}
</div>
)}
<div className="mx-auto flex max-w-5xl flex-wrap items-center justify-center gap-3">
{contributors.map((contributor, index) => (
<motion.div
key={index}
viewport={{ once: true }}
initial={{ opacity: 0, scale: 0 }}
whileInView={{ opacity: 1, scale: 1, transition: { delay: index * 0.025 } }}
className={cn(index > 30 && "hidden lg:block")}
>
<a href={contributor.url} target="_blank" rel="noreferrer">
<Tooltip content={contributor.name}>
<Avatar>
<AvatarImage src={contributor.avatar} alt={contributor.name} />
<AvatarFallback>{contributor.name}</AvatarFallback>
</Avatar>
</Tooltip>
</a>
</motion.div>
))}
</div>
</section>
);
};

View File

@ -0,0 +1,247 @@
/* eslint-disable lingui/text-restrictions */
/* eslint-disable lingui/no-unlocalized-strings */
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
import { useLanguages } from "@/client/services/resume/translation";
// Who are you, and why did you build Reactive Resume?
const Question1 = () => (
<AccordionItem value="1">
<AccordionTrigger>Who are you, and why did you build Reactive Resume?</AccordionTrigger>
<AccordionContent className="prose max-w-none dark:prose-invert">
<p>
I'm Amruth Pillai, just another run-off-the-mill developer working at Elara Digital GmbH in
Berlin, Germany. I'm married to my beautiful and insanely supportive wife who has helped me
in more ways than one in seeing this project to it's fruition. I am originally from
Bengaluru, India where I was a developer at Postman (the API testing tool) for a short
while.
</p>
<p>
Back in my university days, I designed a really cool dark mode resume (link on my website)
using Figma and I had a line of friends and strangers asking me to design their resume for
them.
</p>
<p>
While I could have charged everyone a hefty sum and retired even before I began, I decided
to build the first version of Reactive Resume in 2019. Since then, it's gone through
multiple iterations as I've learned a lot of better coding practices over the years.
</p>
<p>
At the time of writing, Reactive Resume is probably one of the only handful of resume
builders out there available to the world for free and without an annoying paywall at the
end. While being free is often associated with software that's not of good quality, I strive
to prove them wrong and build a product that people love using and are benefitted by it.
</p>
<p>
My dream has always been to build something that at least a handful people use on a daily
basis, and I'm extremely proud to say that Reactive Resume, over it's years of development,
has **helped over half a million people build their resume**, and I hope it only increases
from here and reaches more people who are in need of a good resume to kickstart their career
but can't afford to pay for one.
</p>
</AccordionContent>
</AccordionItem>
);
// How much does it cost to run Reactive Resume?
const Question2 = () => (
<AccordionItem value="2">
<AccordionTrigger>How much does it cost to run Reactive Resume?</AccordionTrigger>
<AccordionContent className="prose max-w-none dark:prose-invert">
<p>
It's not much honestly.{" "}
<a href="https://pillai.xyz/digitalocean" rel="noreferrer" target="_blank">
DigitalOcean
</a>{" "}
has graciously sponsored their infrastructure to allow me to host Reactive Resume on their
platform. There's only the fee I pay to dependent services to send emails, renew the domain,
etc.
</p>
<p>
I've spent countless hours and sleepless nights building the application though, and I
honestly do not expect anything in return but to hear from you on how the app has helped you
with your career.
</p>
<p>
But if you do feel like supporting the developer and the future development of Reactive
Resume, please donate (<em>only if you have some extra money lying around</em>) on my{" "}
<a href="https://github.com/sponsors/AmruthPillai/" rel="noreferrer" target="_blank">
GitHub Sponsors page
</a>
. You can choose to donate one-time or sponsor a recurring donation.
</p>
<p>
Alternatively, if you are in the US, or you are a part of a large educational institution or
corporate organization, you can{" "}
<a href="https://opencollective.com/reactive-resume" rel="noreferrer" target="_blank">
support the project through Open Collective
</a>
. We are fiscally hosted through Open Collective Europe, which means your donations and
sponsorships could also be made tax-deductible.
</p>
</AccordionContent>
</AccordionItem>
);
// Other than donating, how can I support you?
const Question3 = () => (
<AccordionItem value="3">
<AccordionTrigger>Other than donating, how can I support you?</AccordionTrigger>
<AccordionContent className="prose max-w-none dark:prose-invert">
<p>
<strong>If you speak a language other than English</strong>, sign up to be a translator on
Crowdin, our translation management service. You can help translate the product to your
language and share it among your community. Even if the language is already translated, it
helps to sign up as you would be notified when there are new phrases to be translated.
</p>
<p>
<strong>If you work in the media, are an influencer or have lots of friends</strong>, share
the app with your circles and let them know so it can reach the people who need it the most.
I'm also <a href="mailto:hello@amruthpillai.com">open to giving tech talks</a>, although
that's wishful thinking. But if you do mention Reactive Resume on your blog, let me know so
that I can link back to you here.
</p>
<p>
<strong>If you found a bug or have an idea for a feature</strong>, raise an issue on GitHub
or shoot me a message and let me know what you'd like to see. I can't promise that it'll be
done soon, but juggling work, life and open-source, I'll definitely get to it when I can.
</p>
</AccordionContent>
</AccordionItem>
);
// What languages are supported on Reactive Resume?
const Question4 = () => {
const { languages } = useLanguages();
return (
<AccordionItem value="4">
<AccordionTrigger>What languages are supported on Reactive Resume?</AccordionTrigger>
<AccordionContent className="prose max-w-none dark:prose-invert">
<p>
Here are the languages currently supported by Reactive Resume, along with their respective
completion percentages.
</p>
<div className="flex flex-wrap items-start justify-start gap-x-2 gap-y-4">
{languages.map((language) => (
<a
key={language.id}
target="_blank"
rel="noreferrer"
className="no-underline"
href={`https://crowdin.com/translate/reactive-resume/all/en-${language.editorCode}`}
>
<div className="relative bg-secondary-accent font-medium">
<span className="px-2 py-1">{language.name}</span>
<span
className={cn(
"inset-0 bg-warning px-1.5 py-1 text-xs text-white",
language.progress < 40 && "bg-error",
language.progress > 80 && "bg-success",
)}
>
{language.progress}%
</span>
</div>
</a>
))}
</div>
<p>
If you'd like to improve the translations for your language, please{" "}
<a href="https://crowdin.com/project/reactive-resume" rel="noreferrer" target="_blank">
sign up as a translator on Crowdin
</a>{" "}
and join the project. You can also choose to be notified of any new phrases that get added
to the app.
</p>
<p>
If a language is missing from this list, please raise an issue on GitHub requesting its
inclusion, and I will make sure to add it as soon as possible.
</p>
</AccordionContent>
</AccordionItem>
);
};
// How does the OpenAI Integration work?
const Question5 = () => (
<AccordionItem value="5">
<AccordionTrigger>How does the OpenAI Integration work?</AccordionTrigger>
<AccordionContent className="prose max-w-none dark:prose-invert">
<p>
OpenAI has been a game-changer for all of us. I cannot tell you how much ChatGPT has helped
me in my everyday work and with the development of Reactive Resume. It only makes sense that
you leverage what AI has to offer and let it help you build the perfect resume.
</p>
<p>
While most applications out there charge you a fee to use their AI services (rightfully so,
because it isn't cheap), you can choose to enter your own OpenAI API key on the Settings
page (under OpenAI Integration).{" "}
<strong>The key is stored in your browser's local storage</strong>, which means that if you
uninstall your browser, or even clear your data, the key is gone with it. All requests made
to OpenAI are also sent directly to their service and does not hit the app servers at all.
</p>
<p>
The policy behind "bring your own key" (BYOK) is{" "}
<a href="https://community.openai.com/t/openais-bring-your-own-key-policy/14538/46">
still being discussed
</a>{" "}
and probably might change over a period of time, but while it's available, I would keep the
feature on the app.
</p>
<p>
You are free to turn off all AI features (and not be aware of it's existence) simply by not
adding a key in the Settings page and still make use of all of the useful features that
Reactive Resume has to offer. I would even suggest you to take the extra step of using
ChatGPT to write your content, and simply copy it over to Reactive Resume.
</p>
</AccordionContent>
</AccordionItem>
);
export const FAQSection = () => {
return (
<section id="faq" className="container relative py-24 sm:py-32">
<div className="grid grid-cols-3 gap-x-12">
<div className="col-span-1 space-y-6">
<h2 className="text-3xl font-bold">Frequently Asked Questions</h2>
<p className="text-base leading-loose">
Here are some questions I often get asked about Reactive Resume.
</p>
<p className="text-sm leading-loose">
Unfortunately, this section is available only in English, as I do not want to burden
translators with having to translate these large paragraphs of text.
</p>
</div>
<div className="col-span-2">
<Accordion collapsible type="single">
<Question1 />
<Question2 />
<Question3 />
<Question4 />
<Question5 />
</Accordion>
</div>
</div>
</section>
);
};

View File

@ -0,0 +1,140 @@
import { t } from "@lingui/macro";
import {
Brain,
Cloud,
CloudSun,
CurrencyDollarSimple,
EnvelopeSimple,
Eye,
File,
Files,
Folder,
GitBranch,
GithubLogo,
GoogleChromeLogo,
GoogleLogo,
IconContext,
Layout,
Lock,
Note,
Prohibit,
Scales,
StackSimple,
Star,
Swatches,
TextAa,
Translate,
} from "@phosphor-icons/react";
import { cn } from "@reactive-resume/utils";
import { motion } from "framer-motion";
type Feature = {
icon: React.ReactNode;
title: string;
className?: string;
};
const featureLabel = cn(
"flex cursor-default items-center justify-center gap-x-2 rounded bg-secondary px-4 py-3 text-sm font-medium leading-none text-primary transition-colors hover:bg-primary hover:text-background",
);
export const FeaturesSection = () => {
const features: Feature[] = [
{ icon: <CurrencyDollarSimple />, title: t`Free, forever` },
{ icon: <GitBranch />, title: t`Open Source` },
{ icon: <Scales />, title: t`MIT License` },
{ icon: <Prohibit />, title: t`No user tracking or advertising` },
{ icon: <Cloud />, title: t`Self-host with Docker` },
{ icon: <Translate />, title: t`Available in 20+ languages` },
{ icon: <Brain />, title: t`OpenAI Integration` },
{ icon: <GithubLogo />, title: t`Sign in with GitHub` },
{ icon: <GoogleLogo />, title: t`Sign in with Google` },
{ icon: <EnvelopeSimple />, title: t`Sign in with Email` },
{ icon: <Lock />, title: t`Secure with two-factor authentication` },
{ icon: <StackSimple />, title: t`8 design templates to choose from, more on the way` },
{ icon: <Files />, title: t`Design single/multi page resumes` },
{ icon: <Folder />, title: t`Manage multiple resumes` },
{ icon: <Swatches />, title: t`Customisable colour palettes` },
{ icon: <Layout />, title: t`Customisable layouts` },
{ icon: <Star />, title: t`Custom resume sections` },
{ icon: <Note />, title: t`Personal notes for each resume` },
{ icon: <Lock />, title: t`Lock a resume to prevent editing` },
{ icon: <File />, title: t`Supports A4/Letter page formats` },
{ icon: <TextAa />, title: t`Pick any font from Google Fonts` },
{ icon: <GoogleChromeLogo />, title: t`Host your resume publicly` },
{ icon: <Eye />, title: t`Track views and downloads` },
{ icon: <CloudSun />, title: t`Light or dark theme` },
{
icon: (
<div className="flex items-center space-x-1">
<img src="https://cdn.simpleicons.org/react" alt="React" width={14} height={14} />
<img src="https://cdn.simpleicons.org/vite" alt="Vite" width={14} height={14} />
<img
src="https://cdn.simpleicons.org/tailwindcss"
alt="TailwindCSS"
width={14}
height={14}
/>
<img src="https://cdn.simpleicons.org/nestjs" alt="NestJS" width={14} height={14} />
<img
src="https://cdn.simpleicons.org/googlechrome"
alt="Google Chrome"
width={14}
height={14}
/>
<img
src="https://cdn.simpleicons.org/postgresql"
alt="PostgreSQL"
width={14}
height={14}
/>
<img src="https://cdn.simpleicons.org/redis" alt="Redis" width={14} height={14} />
</div>
),
title: t`Powered by`,
className: "flex-row-reverse",
},
];
return (
<section id="features" className="relative bg-secondary-accent py-24 sm:py-32">
<div className="container">
<div className="space-y-6 leading-loose">
<h2 className="text-4xl font-bold">{t`Rich in features, not in pricing.`}</h2>
<p className="max-w-4xl text-base leading-relaxed">
{t`Reactive Resume is a passion project of over 3 years of hard work, and with that comes a number of re-iterated ideas and features that have been built to (near) perfection.`}
</p>
<IconContext.Provider value={{ size: 14, weight: "bold" }}>
<div className="!mt-12 flex flex-wrap items-center gap-4">
{features.map((feature, index) => (
<motion.div
key={index}
viewport={{ once: true }}
initial={{ opacity: 0, x: -50 }}
whileInView={{ opacity: 1, x: 0, transition: { delay: index * 0.1 } }}
className={cn(featureLabel, feature.className, index > 8 && "hidden lg:flex")}
>
{feature.icon}
<h4>{feature.title}</h4>
</motion.div>
))}
<motion.p
viewport={{ once: true }}
initial={{ opacity: 0, x: -50 }}
whileInView={{
opacity: 1,
x: 0,
transition: { delay: (features.length + 1) * 0.1 },
}}
>
{t`and many more...`}
</motion.p>
</div>
</IconContext.Provider>
</div>
</div>
</section>
);
};

View File

@ -10,13 +10,14 @@ import { HeroCTA } from "./call-to-action";
import { Decoration } from "./decoration";
export const HeroSection = () => (
<section className="relative">
<section id="hero" className="relative">
<Decoration.Grid />
<Decoration.Gradient />
<div className="mx-auto max-w-7xl px-6 lg:flex lg:h-screen lg:items-center lg:px-12">
<motion.div
className="mx-auto max-w-3xl shrink-0 lg:mx-0 lg:max-w-xl lg:pt-8"
viewport={{ once: true }}
initial={{ opacity: 0, x: -100 }}
whileInView={{ opacity: 1, x: 0 }}
>
@ -47,13 +48,17 @@ export const HeroSection = () => (
<div className="mx-auto mt-16 flex max-w-2xl sm:mt-24 lg:ml-10 lg:mr-0 lg:mt-0 lg:max-w-none lg:flex-none xl:ml-20">
<div className="max-w-3xl flex-none sm:max-w-5xl lg:max-w-none">
<motion.div initial={{ opacity: 0, x: 100 }} whileInView={{ opacity: 1, x: 0 }}>
<motion.div
viewport={{ once: true }}
initial={{ opacity: 0, x: 100 }}
whileInView={{ opacity: 1, x: 0 }}
>
<Tilt {...defaultTiltProps}>
<img
width={3600}
height={2078}
src="/screenshots/builder.png"
alt={t`Reactive Resume - Screenshot - Builder Screen`}
alt="Reactive Resume - Screenshot - Builder Screen"
className="w-[76rem] rounded-lg bg-background/5 shadow-2xl ring-1 ring-foreground/10"
/>
</Tilt>

View File

@ -33,7 +33,7 @@ const Logo = ({ company }: LogoProps) => (
const logoList: string[] = ["amazon", "google", "postman", "twilio", "zalando"];
export const LogoCloudSection = () => (
<section className="relative py-24 sm:py-32">
<section id="logo-cloud" className="relative py-24 sm:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<p className="text-center text-lg leading-relaxed">
{t`Reactive Resume has helped people land jobs at these great companies:`}

View File

@ -0,0 +1,59 @@
import { t } from "@lingui/macro";
import { motion } from "framer-motion";
const resumes = [
"/sample-resumes/ditto",
"/sample-resumes/ditto",
"/sample-resumes/ditto",
"/sample-resumes/ditto",
];
export const SampleResumesSection = () => (
<section id="sample-resumes" className="relative py-24 sm:py-32">
<div className="container flex flex-col gap-12 lg:min-h-[600px] lg:flex-row lg:items-start">
<div className="space-y-4 lg:mt-16 lg:basis-96">
<h2 className="text-4xl font-bold">{t`Sample Resumes`}</h2>
<p className="text-base leading-relaxed">
{t`Have a look at some of the resume created to showcase the templates available on Reactive Resume. They also serve some great examples to help guide the creation of your own resume.`}
</p>
</div>
<div className="w-full overflow-hidden lg:absolute lg:right-0 lg:max-w-[55%]">
<motion.div
animate={{
x: [0, -400],
transition: {
x: {
duration: 30,
repeat: Infinity,
repeatType: "mirror",
},
},
}}
className="flex items-center gap-x-6"
>
{resumes.map((resume, index) => (
<motion.a
key={index}
target="_blank"
rel="noreferrer"
href={`${resume}.pdf`}
className="max-w-none flex-none"
viewport={{ once: true }}
initial={{ opacity: 0, x: -100 }}
whileInView={{ opacity: 1, x: 0, transition: { delay: (index + 1) * 0.5 } }}
>
<img
alt={resume}
src={`${resume}.jpg`}
className=" aspect-[1/1.4142] h-[400px] rounded object-cover lg:h-[600px]"
/>
</motion.a>
))}
</motion.div>
<div className="pointer-events-none absolute inset-y-0 left-0 hidden w-1/2 bg-gradient-to-r from-background to-transparent lg:block" />
</div>
</div>
</section>
);

View File

@ -1,40 +1,16 @@
import { animate, motion } from "framer-motion";
import { useEffect, useRef, useState } from "react";
import { animate, motion, useInView } from "framer-motion";
import { useEffect, useRef } from "react";
type CounterProps = { from: number; to: number };
export const Counter = ({ from, to }: CounterProps) => {
const [isInView, setIsInView] = useState(false);
const nodeRef = useRef<HTMLParagraphElement | null>(null);
const isInView = useInView(nodeRef, { once: true });
useEffect(() => {
const node = nodeRef.current;
if (!node) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsInView(true);
}
});
},
{ threshold: 0.1 },
);
observer.observe(node);
return () => {
observer.unobserve(node);
};
}, []);
useEffect(() => {
if (!isInView) return;
const node = nodeRef.current;
if (!node) return;
if (!isInView || !node) return;
const controls = animate(from, to, {
duration: 1,
@ -51,7 +27,7 @@ export const Counter = ({ from, to }: CounterProps) => {
ref={nodeRef}
transition={{ duration: 0.5 }}
initial={{ opacity: 0, scale: 0.1 }}
animate={isInView ? { opacity: 1, scale: 1 } : {}}
whileInView={{ opacity: 1, scale: 1 }}
/>
);
};

View File

@ -15,7 +15,7 @@ export const StatisticsSection = () => {
];
return (
<section className="relative py-24 sm:py-32">
<section id="statistics" className="relative py-24 sm:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<dl className="grid grid-cols-1 gap-x-8 gap-y-16 text-center lg:grid-cols-3">
{stats.map((stat, index) => (

View File

@ -0,0 +1,88 @@
import { t } from "@lingui/macro";
export const SupportSection = () => (
<section
id="support"
className="relative space-y-12 bg-secondary-accent py-24 text-primary sm:py-32"
>
<div className="container space-y-6">
<h1 className="text-4xl font-bold">{t`Supporting Reactive Resume`}</h1>
<p className="max-w-4xl leading-loose">
{t`Reactive Resume is a free and open-source project crafted mostly by me, and your support would be greatly appreciated. If you're inclined to contribute, and only if you can afford to, consider making a donation through any of the listed platforms. Additionally, donations to Reactive Resume through Open Collective are tax-exempt, as the project is fiscally hosted by Open Collective Europe.`}
</p>
<div className="flex items-center gap-x-10">
<a
href="https://github.com/sponsors/AmruthPillai"
rel="noreferrer noopener nofollow"
target="_blank"
>
<img
src="/support-logos/github-sponsors-light.svg"
className="hidden max-h-[42px] dark:block"
// eslint-disable-next-line lingui/no-unlocalized-strings
alt="GitHub Sponsors"
/>
<img
src="/support-logos/github-sponsors-dark.svg"
className="block max-h-[42px] dark:hidden"
// eslint-disable-next-line lingui/no-unlocalized-strings
alt="GitHub Sponsors"
/>
</a>
<a
href="https://opencollective.com/Reactive-Resume"
rel="noreferrer noopener nofollow"
target="_blank"
>
<img
src="/support-logos/open-collective-light.svg"
className="hidden max-h-[38px] dark:block"
// eslint-disable-next-line lingui/no-unlocalized-strings
alt="Open Collective"
/>
<img
src="/support-logos/open-collective-dark.svg"
className="block max-h-[38px] dark:hidden"
// eslint-disable-next-line lingui/no-unlocalized-strings
alt="Open Collective"
/>
</a>
<a href="https://paypal.me/amruthde" rel="noreferrer noopener nofollow" target="_blank">
{/* eslint-disable-next-line lingui/no-unlocalized-strings */}
<img src="/support-logos/paypal.svg" className=" max-h-[28px]" alt="PayPal" />
</a>
</div>
<p className="max-w-4xl leading-loose">
{t`If you're multilingual, we'd love your help in bringing the app to more languages and
communities. Don't worry if you don't see your language on the list - just give me a
shout-out on GitHub, and I'll make sure to include it. Ready to get started? Jump into
translation over at Crowdin by clicking the link below.`}
</p>
<div className="flex items-center gap-x-10">
<img
src="/support-logos/crowdin-light.svg"
className="hidden max-h-[32px] dark:block"
// eslint-disable-next-line lingui/no-unlocalized-strings
alt="Crowdin"
/>
<img
src="/support-logos/crowdin-dark.svg"
className="block max-h-[32px] dark:hidden"
// eslint-disable-next-line lingui/no-unlocalized-strings
alt="Crowdin"
/>
</div>
<p className="max-w-4xl leading-loose">
{t`Even if you're not in a position to contribute financially, you can still make a difference by
giving the GitHub repository a star, spreading the word to your friends, or dropping a quick
message to let me know how Reactive Resume has helped you. Your feedback and support are
always welcome and much appreciated!`}
</p>
</div>
</section>
);

View File

@ -0,0 +1,106 @@
/* eslint-disable lingui/text-restrictions */
/* eslint-disable lingui/no-unlocalized-strings */
import { t, Trans } from "@lingui/macro";
import { Quotes } from "@phosphor-icons/react";
import { cn } from "@reactive-resume/utils";
import { motion } from "framer-motion";
const email = "hello@amruthpillai.com";
type Testimonial = {
quote: string;
name: string;
};
const testimonials: Testimonial[][] = [
[
{
name: "N. Elnour",
quote:
"This is really a thank you for Reactive Resume. Drafting resumes was never a strength of mine, so your app really made the whole process easy and smooth!",
},
{
name: "S. Bhaije",
quote:
"Hi Amruth! First off, many thanks for making RxResume! This is one of the best resume-building tools I have ever found. Have also recommended it to many of my university friends...",
},
{
name: "K. Lietzau",
quote:
"Hi, I just found your resume builder, and I just want to say, I really appreciate it! The moment I saw it was open source, I closed all the other CV sites I was considering. Thank you for your service.",
},
],
[
{
name: "R. Sinnot",
quote:
"Hey, Just wanted to let you know you not only helped me get a job, you helped my partner and my childhood friend, who then used your site to help one of her friends get a job. I sponsored you on Github to give back a bit but I wanted to let you know you really made a difference with your resume builder.",
},
{
name: "P. Jignesh",
quote:
"Hey, I am a Mechanical engineer, not understand coding, messy AI, and computer systems, But wait, what drags me here is your creativity, Your website RxResume is all good! using it and the efforts you made to keep this free is remarkable. keeping doing great work.",
},
],
[
{
name: "A. Rehman",
quote:
"Hey Amruth, I have loved your Reactive Resume Website. Thank you so much for making this kind of thing.",
},
{
name: "S. Innocent",
quote:
"First of all, I appreciate your effort for making reactive resume a free tool for the community. Very much better than many premium resume builder...",
},
{
name: "M. Fritza",
quote:
"Hello sir, I just wanted to write a thank you message for developing RxResume. It's easy to use, intuitive and it's much more practical than many others that made you pay up after spending an hour to create your CV. I'll be sure to buy you a coffee after I get my first job. I wish you everything best in life!",
},
],
];
export const TestimonialsSection = () => (
<section id="testimonials" className="container relative space-y-12 py-24 sm:py-32">
<div className="space-y-6 text-center">
<h1 className="text-4xl font-bold">{t`Testimonials`}</h1>
<p className="mx-auto max-w-2xl leading-relaxed">
<Trans>
I always love to hear from the users of Reactive Resume with feedback or support. Here are
some of the messages I've received. If you have any feedback, feel free to drop me an
email at{" "}
<a href={email} className="underline">
{email}
</a>
.
</Trans>
</p>
</div>
<div className="grid grid-cols-1 gap-8 lg:grid-cols-3 lg:gap-y-0">
{testimonials.map((columnGroup, groupIndex) => (
<div key={groupIndex} className="space-y-8">
{columnGroup.map((testimonial, index) => (
<motion.figure
key={index}
initial={{ opacity: 0, y: -100 }}
animate={{ opacity: 1, y: 0, transition: { delay: index * 0.25 } }}
className={cn(
"relative overflow-hidden rounded-lg bg-secondary-accent p-5 text-primary shadow-lg",
index > 0 && "hidden lg:block",
)}
>
<Quotes size={64} className="absolute -right-3 bottom-0 opacity-20" />
<blockquote className="italic leading-relaxed">
&ldquo;{testimonial.quote}&rdquo;
</blockquote>
<figcaption className="mt-3 font-medium">{testimonial.name}</figcaption>
</motion.figure>
))}
</div>
))}
</div>
</section>
);

View File

@ -1,34 +1,44 @@
import "@/client/libs/dayjs";
import { i18n } from "@lingui/core";
import { detect, fromNavigator, fromUrl } from "@lingui/detect-locale";
import { detect, fromNavigator, fromStorage, fromUrl } from "@lingui/detect-locale";
import { I18nProvider } from "@lingui/react";
import get from "lodash.get";
import { useEffect } from "react";
import { defaultLocale, dynamicActivate } from "../libs/lingui";
import { updateUser } from "../services/user";
import { useAuthStore } from "../stores/auth";
import { useResumeStore } from "../stores/resume";
type Props = {
children: React.ReactNode;
};
export const LocaleProvider = ({ children }: Props) => {
const userLocale = useAuthStore((state) => get(state.user, "locale", null));
const resumeLocale = useResumeStore((state) => get(state.resume, "data.metadata.locale", null));
const userLocale = useAuthStore((state) => state.user?.locale);
useEffect(() => {
const detectedLocale = detect(
resumeLocale,
userLocale,
fromUrl("lang"),
fromUrl("locale"),
fromStorage("locale"),
fromNavigator(),
userLocale,
defaultLocale,
)!;
dynamicActivate(detectedLocale);
}, [userLocale, resumeLocale]);
}, [userLocale]);
return <I18nProvider i18n={i18n}>{children}</I18nProvider>;
};
export const changeLanguage = async (locale: string) => {
// Update locale in local storage
window.localStorage.setItem("locale", locale);
// Update locale in user profile, if authenticated
const state = useAuthStore.getState();
if (state.user) await updateUser({ locale }).catch(() => null);
// Reload the page for language switch to take effect
window.location.reload();
};

View File

@ -12,7 +12,7 @@ export * from "./email-verification/verify-email";
export * from "./password-recovery/forgot-password";
export * from "./password-recovery/reset-password";
// Two Factor Authentication
// Two-Factor Authentication
export * from "./two-factor-authentication/backup-otp";
export * from "./two-factor-authentication/disable";
export * from "./two-factor-authentication/enable";

View File

@ -0,0 +1,41 @@
import { ContributorDto } from "@reactive-resume/dto";
import { useQuery } from "@tanstack/react-query";
import { axios } from "@/client/libs/axios";
export const fetchGitHubContributors = async () => {
const response = await axios.get<ContributorDto[]>(`/contributors/github`);
return response.data;
};
export const fetchCrowdinContributors = async () => {
const response = await axios.get<ContributorDto[]>(`/contributors/crowdin`);
return response.data;
};
export const useContributors = () => {
const {
error: githubError,
isPending: githubLoading,
data: github,
} = useQuery({
queryKey: ["contributors", "github"],
queryFn: fetchGitHubContributors,
});
const {
error: crowdinError,
isPending: crowdinLoading,
data: crowdin,
} = useQuery({
queryKey: ["contributors", "crowdin"],
queryFn: fetchCrowdinContributors,
});
const error = githubError || crowdinError;
const loading = githubLoading || crowdinLoading;
return { github, crowdin, loading, error };
};

View File

@ -0,0 +1,23 @@
import { LanguageDto } from "@reactive-resume/dto";
import { useQuery } from "@tanstack/react-query";
import { axios } from "@/client/libs/axios";
export const fetchLanguages = async () => {
const response = await axios.get<LanguageDto[]>(`/translation/languages`);
return response.data;
};
export const useLanguages = () => {
const {
error,
isPending: loading,
data: languages,
} = useQuery({
queryKey: ["translation", "languages"],
queryFn: fetchLanguages,
});
return { languages: languages ?? [], loading, error };
};

View File

@ -8,6 +8,7 @@ import { join } from "path";
import { AuthModule } from "./auth/auth.module";
import { CacheModule } from "./cache/cache.module";
import { ConfigModule } from "./config/config.module";
import { ContributorsModule } from "./contributors/contributors.module";
import { DatabaseModule } from "./database/database.module";
import { HealthModule } from "./health/health.module";
import { MailModule } from "./mail/mail.module";
@ -36,6 +37,7 @@ import { UtilsModule } from "./utils/utils.module";
StorageModule,
PrinterModule,
TranslationModule,
ContributorsModule,
// Static Assets
ServeStaticModule.forRoot({

View File

@ -166,7 +166,7 @@ export class AuthController {
response.status(200).send(data);
}
// Two Factor Authentication Flows
// Two-Factor Authentication Flows
@ApiTags("Two-Factor Auth")
@Post("2fa/setup")
@UseGuards(JwtGuard)

View File

@ -210,7 +210,7 @@ export class AuthService {
});
}
// Two Factor Authentication Flows
// Two-Factor Authentication Flows
async setup2FASecret(email: string) {
// If the user already has 2FA enabled, throw an error
const user = await this.userService.findOneByIdentifier(email);

View File

@ -43,7 +43,9 @@ export const configSchema = z.object({
SENTRY_DSN: z.string().url().startsWith("https://").optional(),
// Crowdin (Optional)
CROWDIN_PROJECT_ID: z.coerce.number().optional(),
CROWDIN_DISTRIBUTION_HASH: z.string().optional(),
CROWDIN_ACCESS_TOKEN: z.string().optional(),
// GitHub (OAuth)
GITHUB_CLIENT_ID: z.string().optional(),

View File

@ -0,0 +1,30 @@
import { Controller, Get } from "@nestjs/common";
import { UtilsService } from "../utils/utils.service";
import { ContributorsService } from "./contributors.service";
@Controller("contributors")
export class ContributorsController {
constructor(
private readonly contributorsService: ContributorsService,
private readonly utils: UtilsService,
) {}
@Get("/github")
async githubContributors() {
return this.utils.getCachedOrSet(
`contributors:github`,
async () => this.contributorsService.fetchGitHubContributors(),
1000 * 60 * 60 * 24, // 24 hours
);
}
@Get("/crowdin")
async crowdinContributors() {
return this.utils.getCachedOrSet(
`contributors:crowdin`,
async () => this.contributorsService.fetchCrowdinContributors(),
1000 * 60 * 60 * 24, // 24 hours
);
}
}

View File

@ -0,0 +1,12 @@
import { HttpModule } from "@nestjs/axios";
import { Module } from "@nestjs/common";
import { ContributorsController } from "./contributors.controller";
import { ContributorsService } from "./contributors.service";
@Module({
imports: [HttpModule],
controllers: [ContributorsController],
providers: [ContributorsService],
})
export class ContributorsModule {}

View File

@ -0,0 +1,55 @@
import { HttpService } from "@nestjs/axios";
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { ContributorDto } from "@reactive-resume/dto";
import { Config } from "../config/schema";
type GitHubResponse = { id: number; login: string; html_url: string; avatar_url: string }[];
type CrowdinContributorsResponse = {
data: { data: { id: number; username: string; avatarUrl: string } }[];
};
@Injectable()
export class ContributorsService {
constructor(
private readonly httpService: HttpService,
private readonly configService: ConfigService<Config>,
) {}
async fetchGitHubContributors() {
const response = await this.httpService.axiosRef.get(
`https://api.github.com/repos/AmruthPillai/Reactive-Resume/contributors`,
);
const data = response.data as GitHubResponse;
return data.map((user) => {
return {
id: user.id,
name: user.login,
url: user.html_url,
avatar: user.avatar_url,
} satisfies ContributorDto;
});
}
async fetchCrowdinContributors() {
const projectId = this.configService.get("CROWDIN_PROJECT_ID");
const accessToken = this.configService.get("CROWDIN_ACCESS_TOKEN");
const response = await this.httpService.axiosRef.get(
`https://api.crowdin.com/api/v2/projects/${projectId}/members`,
{ headers: { Authorization: `Bearer ${accessToken}` } },
);
const { data } = response.data as CrowdinContributorsResponse;
return data.map(({ data }) => {
return {
id: data.id,
name: data.username,
url: `https://crowdin.com/profile/${data.username}`,
avatar: data.avatarUrl,
} satisfies ContributorDto;
});
}
}

View File

@ -133,7 +133,7 @@ export class PrinterService {
return tempHtml;
}, pageElement);
pagesBuffer.push(await page.pdf({ width, height }));
pagesBuffer.push(await page.pdf({ width, height, printBackground: true }));
await page.evaluate((tempHtml: string) => {
document.body.innerHTML = tempHtml;

View File

@ -126,7 +126,6 @@ export class ResumeController {
return { url };
} catch (error) {
console.log(error);
Logger.error(error);
throw new InternalServerErrorException(error);
}

View File

@ -1,34 +1,31 @@
import { HttpService } from "@nestjs/axios";
import { Controller, Get, Header, Param } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Config } from "../config/schema";
import { UtilsService } from "../utils/utils.service";
import { TranslationService } from "./translation.service";
@Controller("translation")
export class TranslationController {
constructor(
private readonly httpService: HttpService,
private readonly configService: ConfigService<Config>,
private readonly translationService: TranslationService,
private readonly utils: UtilsService,
) {}
private async fetchTranslations(locale: string) {
const distributionHash = this.configService.get("CROWDIN_DISTRIBUTION_HASH");
const response = await this.httpService.axiosRef.get(
`https://distributions.crowdin.net/${distributionHash}/content/${locale}/messages.json`,
@Get("/languages")
async languages() {
return this.utils.getCachedOrSet(
`translation:languages`,
async () => this.translationService.fetchLanguages(),
1000 * 60 * 60 * 24, // 24 hours
);
return response.data;
}
@Get("/:locale")
@Header("Content-Type", "application/octet-stream")
@Header("Content-Disposition", 'attachment; filename="messages.po"')
async getTranslation(@Param("locale") locale: string) {
async translation(@Param("locale") locale: string) {
return this.utils.getCachedOrSet(
`translation:${locale}`,
async () => this.fetchTranslations(locale),
async () => this.translationService.fetchTranslations(locale),
1000 * 60 * 60 * 24, // 24 hours
);
}

View File

@ -2,9 +2,11 @@ import { HttpModule } from "@nestjs/axios";
import { Module } from "@nestjs/common";
import { TranslationController } from "./translation.controller";
import { TranslationService } from "./translation.service";
@Module({
imports: [HttpModule],
controllers: [TranslationController],
providers: [TranslationService],
})
export class TranslationModule {}

View File

@ -0,0 +1,53 @@
import { HttpService } from "@nestjs/axios";
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { LanguageDto } from "@reactive-resume/dto";
import { Config } from "../config/schema";
type CrowdinResponse = {
data: {
data: {
language: { id: string; name: string; locale: string; editorCode: string };
translationProgress: number;
};
}[];
};
@Injectable()
export class TranslationService {
constructor(
private readonly httpService: HttpService,
private readonly configService: ConfigService<Config>,
) {}
async fetchTranslations(locale: string) {
const distributionHash = this.configService.get("CROWDIN_DISTRIBUTION_HASH");
const response = await this.httpService.axiosRef.get(
`https://distributions.crowdin.net/${distributionHash}/content/${locale}/messages.json`,
);
return response.data;
}
async fetchLanguages() {
const projectId = this.configService.get("CROWDIN_PROJECT_ID");
const accessToken = this.configService.get("CROWDIN_ACCESS_TOKEN");
const response = await this.httpService.axiosRef.get(
`https://api.crowdin.com/api/v2/projects/${projectId}/languages/progress?limit=100`,
{ headers: { Authorization: `Bearer ${accessToken}` } },
);
const { data } = response.data as CrowdinResponse;
return data.map(({ data }) => {
return {
id: data.language.id,
name: data.language.name,
progress: data.translationProgress,
editorCode: data.language.editorCode,
locale: data.language.locale,
} satisfies LanguageDto;
});
}
}

View File

@ -0,0 +1,21 @@
import { createZodDto } from "nestjs-zod/dto";
import { z } from "nestjs-zod/z";
export const contributorSchema = z.object({
id: z.number(),
name: z.string(),
url: z.string(),
avatar: z.string(),
});
export class ContributorDto extends createZodDto(contributorSchema) {}
export const languageSchema = z.object({
id: z.string(),
name: z.string(),
locale: z.string(),
editorCode: z.string(),
progress: z.number(),
});
export class LanguageDto extends createZodDto(languageSchema) {}

View File

@ -1,4 +1,5 @@
export * from "./auth";
export * from "./contributors";
export * from "./resume";
export * from "./statistics";
export * from "./user";

View File

@ -9,7 +9,6 @@ export const defaultLayout = [
// Schema
export const metadataSchema = z.object({
locale: z.string().default("en-US"),
template: z.string().default("rhyhorn"),
layout: z.array(z.array(z.array(z.string()))).default(defaultLayout), // pages -> columns -> sections
css: z.object({
@ -37,6 +36,7 @@ export const metadataSchema = z.object({
size: z.number().default(14),
}),
lineHeight: z.number().default(1.5),
hideIcons: z.boolean().default(false),
underlineLinks: z.boolean().default(true),
}),
notes: z.string().default(""),
@ -47,7 +47,6 @@ export type Metadata = z.infer<typeof metadataSchema>;
// Defaults
export const defaultMetadata: Metadata = {
locale: "en-US",
template: "rhyhorn",
layout: defaultLayout,
css: {
@ -75,6 +74,7 @@ export const defaultMetadata: Metadata = {
size: 14,
},
lineHeight: 1.5,
hideIcons: false,
underlineLinks: true,
},
notes: "",

View File

@ -452,7 +452,6 @@ export const sampleResume: ResumeData = {
},
},
metadata: {
locale: "en-US",
template: "pikachu",
layout: [
[
@ -490,6 +489,7 @@ export const sampleResume: ResumeData = {
size: 13,
},
lineHeight: 2,
hideIcons: false,
underlineLinks: true,
},
notes:

View File

@ -36,7 +36,7 @@ type ComboboxPropsMultiple = {
export type ComboboxProps = ComboboxPropsSingle | ComboboxPropsMultiple;
export const handleSingleSelect = (props: ComboboxPropsSingle, option: ComboboxOption) => {
const handleSingleSelect = (props: ComboboxPropsSingle, option: ComboboxOption) => {
if (props.clearable) {
props.onValueChange?.(option.value === props.value ? "" : option.value);
} else {
@ -44,7 +44,7 @@ export const handleSingleSelect = (props: ComboboxPropsSingle, option: ComboboxO
}
};
export const handleMultipleSelect = (props: ComboboxPropsMultiple, option: ComboboxOption) => {
const handleMultipleSelect = (props: ComboboxPropsMultiple, option: ComboboxOption) => {
if (props.value?.includes(option.value)) {
if (!props.clearable && props.value.length === 1) return false;
props.onValueChange?.(props.value.filter((value) => value !== option.value));

View File

@ -4,11 +4,60 @@ const config: LinguiConfig = {
format: "po",
sourceLocale: "en-US",
pseudoLocale: "zu-ZA",
locales: ["en-US", "de-DE", "zu-ZA"],
fallbackLocales: { default: "en-US" },
locales: [
"af-ZA",
"am-ET",
"ar-SA",
"bg-BG",
"bn-BD",
"ca-ES",
"cs-CZ",
"da-DK",
"de-DE",
"el-GR",
"en-US",
"es-ES",
"fa-IR",
"fi-FI",
"fr-FR",
"he-IL",
"hi-IN",
"hu-HU",
"id-ID",
"it-IT",
"ja-JP",
"km-KH",
"kn-IN",
"ko-KR",
"lt-LT",
"ml-IN",
"mr-IN",
"ne-NP",
"nl-NL",
"no-NO",
"or-IN",
"pl-PL",
"pt-BR",
"pt-PT",
"ro-RO",
"ru-RU",
"sr-SP",
"sv-SE",
"ta-IN",
"te-IN",
"th-TH",
"tr-TR",
"uk-UA",
"vi-VN",
"zh-CN",
"zh-TW",
"zu-ZA",
],
catalogs: [
{
path: "<rootDir>/apps/client/src/locales/{locale}/messages",
include: ["<rootDir>/apps/client/src"],
path: "<rootDir>/apps/client/src/locales/{locale}/messages",
},
],
};

View File

@ -23,12 +23,12 @@
"lint": "nx run-many -t lint --fix",
"format": "pnpm dlx prettier -w .",
"prepare": "pnpm dlx husky install",
"messages:extract": "pnpm exec lingui extract --clean --overwrite"
"messages:extract": "pnpm exec lingui extract --clean --overwrite --locale en-US"
},
"devDependencies": {
"@babel/core": "^7.23.3",
"@babel/preset-react": "^7.23.3",
"@commitlint/cli": "^18.4.0",
"@commitlint/cli": "^18.4.1",
"@commitlint/config-conventional": "^18.4.0",
"@lingui/cli": "^4.5.0",
"@lingui/conf": "^4.5.0",
@ -52,9 +52,9 @@
"@swc/cli": "~0.1.62",
"@swc/core": "~1.3.96",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.6",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"@tanstack/eslint-plugin-query": "^5.6.0",
"@tanstack/eslint-plugin-query": "^5.8.3",
"@testing-library/react": "14.1.0",
"@types/async-retry": "^1.4.8",
"@types/bcryptjs": "^2.4.6",
@ -107,7 +107,7 @@
"postcss": "8.4.31",
"postcss-import": "^15.1.0",
"postcss-nested": "^6.0.1",
"prettier": "^3.0.3",
"prettier": "^3.1.0",
"prisma": "^5.5.2",
"tailwindcss": "^3.3.5",
"tailwindcss-animate": "^1.0.7",
@ -171,7 +171,7 @@
"@songkeys/nestjs-redis": "^10.0.0",
"@songkeys/nestjs-redis-health": "^10.0.0",
"@swc/helpers": "~0.5.3",
"@tanstack/react-query": "^5.8.1",
"@tanstack/react-query": "^5.8.3",
"@tiptap/extension-highlight": "^2.1.12",
"@tiptap/extension-image": "^2.1.12",
"@tiptap/extension-link": "^2.1.12",
@ -206,7 +206,7 @@
"nestjs-prisma": "^0.22.0",
"nestjs-zod": "^3.0.0",
"nodemailer": "^6.9.7",
"openai": "^4.17.3",
"openai": "^4.17.4",
"otplib": "^12.0.1",
"papaparse": "^5.4.1",
"passport": "^0.6.0",
@ -222,8 +222,8 @@
"react-dom": "18.2.0",
"react-helmet-async": "^1.3.0",
"react-hook-form": "^7.48.2",
"react-parallax-tilt": "^1.7.173",
"react-resizable-panels": "^0.0.55",
"react-parallax-tilt": "^1.7.174",
"react-resizable-panels": "^0.0.54",
"react-router-dom": "6.18.0",
"react-zoom-pan-pinch": "^3.3.0",
"reflect-metadata": "^0.1.13",

85
pnpm-lock.yaml generated
View File

@ -162,8 +162,8 @@ dependencies:
specifier: ~0.5.3
version: 0.5.3
'@tanstack/react-query':
specifier: ^5.8.1
version: 5.8.1(react-dom@18.2.0)(react@18.2.0)
specifier: ^5.8.3
version: 5.8.3(react-dom@18.2.0)(react@18.2.0)
'@tiptap/extension-highlight':
specifier: ^2.1.12
version: 2.1.12(@tiptap/core@2.1.12)
@ -267,8 +267,8 @@ dependencies:
specifier: ^6.9.7
version: 6.9.7
openai:
specifier: ^4.17.3
version: 4.17.3
specifier: ^4.17.4
version: 4.17.4
otplib:
specifier: ^12.0.1
version: 12.0.1
@ -315,11 +315,11 @@ dependencies:
specifier: ^7.48.2
version: 7.48.2(react@18.2.0)
react-parallax-tilt:
specifier: ^1.7.173
version: 1.7.173(react-dom@18.2.0)(react@18.2.0)
specifier: ^1.7.174
version: 1.7.174(react-dom@18.2.0)(react@18.2.0)
react-resizable-panels:
specifier: ^0.0.55
version: 0.0.55(react-dom@18.2.0)(react@18.2.0)
specifier: ^0.0.54
version: 0.0.54(react-dom@18.2.0)(react@18.2.0)
react-router-dom:
specifier: 6.18.0
version: 6.18.0(react-dom@18.2.0)(react@18.2.0)
@ -377,8 +377,8 @@ devDependencies:
specifier: ^7.23.3
version: 7.23.3(@babel/core@7.23.3)
'@commitlint/cli':
specifier: ^18.4.0
version: 18.4.0(typescript@5.2.2)
specifier: ^18.4.1
version: 18.4.1(typescript@5.2.2)
'@commitlint/config-conventional':
specifier: ^18.4.0
version: 18.4.0
@ -449,14 +449,14 @@ devDependencies:
specifier: ^0.1.1
version: 0.1.1(tailwindcss@3.3.5)
'@tailwindcss/forms':
specifier: ^0.5.6
version: 0.5.6(tailwindcss@3.3.5)
specifier: ^0.5.7
version: 0.5.7(tailwindcss@3.3.5)
'@tailwindcss/typography':
specifier: ^0.5.10
version: 0.5.10(tailwindcss@3.3.5)
'@tanstack/eslint-plugin-query':
specifier: ^5.6.0
version: 5.6.0(eslint@8.53.0)(typescript@5.2.2)
specifier: ^5.8.3
version: 5.8.3(eslint@8.53.0)(typescript@5.2.2)
'@testing-library/react':
specifier: 14.1.0
version: 14.1.0(react-dom@18.2.0)(react@18.2.0)
@ -573,7 +573,7 @@ devDependencies:
version: 0.2.0(eslint@8.53.0)(typescript@5.2.2)
eslint-plugin-prettier:
specifier: ^5.0.1
version: 5.0.1(eslint-config-prettier@9.0.0)(eslint@8.53.0)(prettier@3.0.3)
version: 5.0.1(eslint-config-prettier@9.0.0)(eslint@8.53.0)(prettier@3.1.0)
eslint-plugin-react:
specifier: 7.33.2
version: 7.33.2(eslint@8.53.0)
@ -614,8 +614,8 @@ devDependencies:
specifier: ^6.0.1
version: 6.0.1(postcss@8.4.31)
prettier:
specifier: ^3.0.3
version: 3.0.3
specifier: ^3.1.0
version: 3.1.0
prisma:
specifier: ^5.5.2
version: 5.5.2
@ -2129,14 +2129,14 @@ packages:
dev: true
optional: true
/@commitlint/cli@18.4.0(typescript@5.2.2):
resolution: {integrity: sha512-iz3KJtmsRRFm6OlyrORxRR6qcrznuoFDzcvMXTMpl6E/wj9Vr2crolDk6cG3bFpJCjWd9C7KidXerRha6hh1kQ==}
/@commitlint/cli@18.4.1(typescript@5.2.2):
resolution: {integrity: sha512-4+jljfd29Udw9RDDyigavLO9LvdbmB8O9xjDzVZ0R3lJuG7nCeyHgnKWIVpFaN590isZMV/cMeQK0gH7hRF40A==}
engines: {node: '>=v18'}
hasBin: true
dependencies:
'@commitlint/format': 18.4.0
'@commitlint/lint': 18.4.0
'@commitlint/load': 18.4.0(typescript@5.2.2)
'@commitlint/load': 18.4.1(typescript@5.2.2)
'@commitlint/read': 18.4.0
'@commitlint/types': 18.4.0
execa: 5.1.1
@ -2206,8 +2206,8 @@ packages:
'@commitlint/types': 18.4.0
dev: true
/@commitlint/load@18.4.0(typescript@5.2.2):
resolution: {integrity: sha512-7unGl1HGRNMgWrUPmj8OFkJyuNUMb6xA1i53/OAFKd9l+U3C4WTfoJe3t/TUz8vKZLCaDcWWR/b2cw5HveBBFg==}
/@commitlint/load@18.4.1(typescript@5.2.2):
resolution: {integrity: sha512-o/plBiPJQgbSq/4ipDpsq4HCmURjBAEjr1EO/p2falr3VhwV0WGXTvb8NlihgI8xtSyO6lHvtycrE535GMLQbA==}
engines: {node: '>=v18'}
dependencies:
'@commitlint/config-validator': 18.4.0
@ -6590,8 +6590,8 @@ packages:
tailwindcss: 3.3.5(ts-node@10.9.1)
dev: true
/@tailwindcss/forms@0.5.6(tailwindcss@3.3.5):
resolution: {integrity: sha512-Fw+2BJ0tmAwK/w01tEFL5TiaJBX1NLT1/YbWgvm7ws3Qcn11kiXxzNTEQDMs5V3mQemhB56l3u0i9dwdzSQldA==}
/@tailwindcss/forms@0.5.7(tailwindcss@3.3.5):
resolution: {integrity: sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==}
peerDependencies:
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
dependencies:
@ -6611,8 +6611,8 @@ packages:
tailwindcss: 3.3.5(ts-node@10.9.1)
dev: true
/@tanstack/eslint-plugin-query@5.6.0(eslint@8.53.0)(typescript@5.2.2):
resolution: {integrity: sha512-A0D8fXIh6fuHcT7e+VaL+QnlLhY9V5QmiaLOTLOIcyVKCpWVZLSHrLP6ghZV6CB+JLalHWCAUF0QW0UaEyyz7g==}
/@tanstack/eslint-plugin-query@5.8.3(eslint@8.53.0)(typescript@5.2.2):
resolution: {integrity: sha512-xq6TqtyTfXXAYeWPRir1Phvt30m2sXk0nJ6m30ejA6WrRW0XpCVnJRMUFhCxUxjLJAhGxJHZmeJ3vkzaXrzHIA==}
peerDependencies:
eslint: ^8.0.0
dependencies:
@ -6623,12 +6623,12 @@ packages:
- typescript
dev: true
/@tanstack/query-core@5.8.1:
resolution: {integrity: sha512-Y0enatz2zQXBAsd7XmajlCs+WaitdR7dIFkqz9Xd7HL4KV04JOigWVreYseTmNH7YFSBSC/BJ9uuNp1MAf+GfA==}
/@tanstack/query-core@5.8.3:
resolution: {integrity: sha512-SWFMFtcHfttLYif6pevnnMYnBvxKf3C+MHMH7bevyYfpXpTMsLB9O6nNGBdWSoPwnZRXFNyNeVZOw25Wmdasow==}
dev: false
/@tanstack/react-query@5.8.1(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-YMagxS8iNPOLg0pK6WOjdSDlAvWKOf69udLOwQrBVmkC2SRLNLko7elo5Ro3ptlJkXvTVHidxC/h5KGi5bH1XQ==}
/@tanstack/react-query@5.8.3(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-EDRrsMgUtKM+SjVmhDYBd4jwWWyHAw3kCaurKLIO90OfPQr/UhpwcqM2l/eQOaUYon9lfDB2ejQi1edHK7zEdA==}
peerDependencies:
react: ^18.0.0
react-dom: ^18.0.0
@ -6639,7 +6639,7 @@ packages:
react-native:
optional: true
dependencies:
'@tanstack/query-core': 5.8.1
'@tanstack/query-core': 5.8.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@ -9994,7 +9994,7 @@ packages:
longest: 2.0.1
word-wrap: 1.2.5
optionalDependencies:
'@commitlint/load': 18.4.0(typescript@5.2.2)
'@commitlint/load': 18.4.1(typescript@5.2.2)
transitivePeerDependencies:
- typescript
dev: true
@ -10972,7 +10972,7 @@ packages:
- typescript
dev: true
/eslint-plugin-prettier@5.0.1(eslint-config-prettier@9.0.0)(eslint@8.53.0)(prettier@3.0.3):
/eslint-plugin-prettier@5.0.1(eslint-config-prettier@9.0.0)(eslint@8.53.0)(prettier@3.1.0):
resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
@ -10988,7 +10988,7 @@ packages:
dependencies:
eslint: 8.53.0
eslint-config-prettier: 9.0.0(eslint@8.53.0)
prettier: 3.0.3
prettier: 3.1.0
prettier-linter-helpers: 1.0.0
synckit: 0.8.5
dev: true
@ -14260,6 +14260,7 @@ packages:
/lodash.mergewith@4.6.2:
resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==}
requiresBuild: true
dev: true
/lodash.once@4.1.1:
@ -15648,8 +15649,8 @@ packages:
is-wsl: 2.2.0
dev: true
/openai@4.17.3:
resolution: {integrity: sha512-Gx9wzl9HWX5pjagkgXVu6U2BTFEPkQFdkppNnAX2n2Rpjtn2zt152wXh7NnZ5eJuVxUGYzRe66JmayAEGjzqAg==}
/openai@4.17.4:
resolution: {integrity: sha512-ThRFkl6snLbcAKS58St7N3CaKuI5WdYUvIjPvf4s+8SdymgNtOfzmZcZXVcCefx04oKFnvZJvIcTh3eAFUUhAQ==}
hasBin: true
dependencies:
'@types/node': 18.18.8
@ -16661,8 +16662,8 @@ packages:
fast-diff: 1.3.0
dev: true
/prettier@3.0.3:
resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==}
/prettier@3.1.0:
resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==}
engines: {node: '>=14'}
hasBin: true
dev: true
@ -17275,8 +17276,8 @@ packages:
/react-is@18.2.0:
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
/react-parallax-tilt@1.7.173(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-uZ+s1/Y8TYnyQGn1qKRTzv/GySBySkh43lWXNO1ArQR3a6/OBamtKqZ3i5fxAph4IkQMgt4gOlK8EVkhLn8q8w==}
/react-parallax-tilt@1.7.174(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-NFmSZ1O+CJ+K+vDqhnNNSREzhJez3sXYX4b12tl00kxvN7jmdngZwCgd5bOtqgyk3OcGAAM43P+pkvxBXrzbSg==}
peerDependencies:
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
@ -17344,8 +17345,8 @@ packages:
use-sidecar: 1.1.2(@types/react@18.2.37)(react@18.2.0)
dev: false
/react-resizable-panels@0.0.55(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-J/LTFzUEjJiqwSjVh8gjUXkQDA8MRPjARASfn++d2+KOgA+9UcRYUfE3QBJixer2vkk+ffQ4cq3QzWzzHgqYpQ==}
/react-resizable-panels@0.0.54(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-f8hHdQrqvXoiZGdRNuoOi/C2cdYT2nEpaOb1KIWVWorSTPZmnE+ZQiamGeu+AMx3iZ/tqBtlAkBOpKXzTnDCoA==}
peerDependencies:
react: ^16.14.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0

View File

@ -15,15 +15,15 @@
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"@/artboard/*": ["apps/artboard/src/*"],
"@/client/*": ["apps/client/src/*"],
"@/server/*": ["apps/server/src/*"],
"@/artboard/*": ["apps/artboard/src/*"],
"@reactive-resume/ui": ["libs/ui/src/index.ts"],
"@reactive-resume/dto": ["libs/dto/src/index.ts"],
"@reactive-resume/utils": ["libs/utils/src/index.ts"],
"@reactive-resume/hooks": ["libs/hooks/src/index.ts"],
"@reactive-resume/parser": ["libs/parser/src/index.ts"],
"@reactive-resume/schema": ["libs/schema/src/index.ts"]
"@reactive-resume/schema": ["libs/schema/src/index.ts"],
"@reactive-resume/ui": ["libs/ui/src/index.ts"],
"@reactive-resume/utils": ["libs/utils/src/index.ts"]
}
},
"exclude": ["node_modules", "dist", "tmp", ".nx"]