├── .eslintcache
├── .gitignore
├── LICENSE
├── README.md
├── components
├── Button
│ ├── Button.jsx
│ └── Button.module.scss
├── ContributionsGraph
│ ├── ContributionsGraph.jsx
│ └── GraphLoading.jsx
├── GithubOAuthButton
│ └── GithubOAuthButton.jsx
├── OGHead
│ └── OGHead.jsx
├── ReportCard
│ ├── ReportCard.jsx
│ └── ReportCardLoading.jsx
├── ShareOnLinkedin
│ ├── ShareOnLinkedIn.jsx
│ └── ShareOnLinkedIn.module.scss
├── ShareOnTwitter
│ ├── ShareOnTwitter.jsx
│ └── ShareOnTwitter.module.scss
└── TrophyModel
│ ├── TrophyModel.jsx
│ └── graph_three_entry.js
├── constants
└── particleOptions.js
├── containers
└── Share
│ └── Share.jsx
├── helpers
├── getUserReport.js
├── sceneToSTL.js
└── statsFormatter.js
├── next.config.js
├── package.json
├── pages
├── [username]
│ └── index.js
├── _app.js
├── _document.js
├── api
│ ├── multitokenizer.js
│ ├── oauth
│ │ ├── login.js
│ │ └── redirect.js
│ ├── report.js
│ └── stats.js
└── index.js
├── public
├── favicon.ico
├── favicon.png
├── icons
│ ├── check.svg
│ ├── download.svg
│ ├── github.svg
│ ├── linkedin.svg
│ └── twitter.svg
├── images
│ └── trophy.png
└── vercel.svg
├── styles
├── Report.module.scss
├── globals.scss
└── landing.scss
└── yarn.lock
/.eslintcache:
--------------------------------------------------------------------------------
1 | [{"D:\\Projects\\git\\year-in-review\\src\\index.js":"1","D:\\Projects\\git\\year-in-review\\src\\App.js":"2","D:\\Projects\\git\\year-in-review\\src\\reportWebVitals.js":"3","D:\\Projects\\git\\year-in-review\\src\\containers\\index.js":"4","D:\\Projects\\git\\year-in-review\\src\\containers\\landing\\index.jsx":"5","D:\\Projects\\git\\year-in-review\\src\\constants\\particles.js":"6","D:\\Projects\\git\\year-in-review\\src\\components\\index.js":"7","D:\\Projects\\git\\year-in-review\\src\\components\\loader\\index.jsx":"8","D:\\Projects\\git\\year-in-review\\src\\components\\landing-header\\index.jsx":"9","D:\\Projects\\git\\year-in-review\\src\\containers\\profile\\index.jsx":"10","D:\\Projects\\git\\year-in-review\\src\\components\\report\\index.jsx":"11","D:\\Projects\\git\\year-in-review\\src\\helpers\\api.js":"12","D:\\Projects\\git\\year-in-review\\src\\helpers\\index.js":"13","D:\\Projects\\git\\year-in-review\\src\\helpers\\useScript.js":"14","D:\\Projects\\git\\github-wrapped\\src\\index.js":"15","D:\\Projects\\git\\github-wrapped\\src\\reportWebVitals.js":"16","D:\\Projects\\git\\github-wrapped\\src\\App.js":"17","D:\\Projects\\git\\github-wrapped\\src\\containers\\index.js":"18","D:\\Projects\\git\\github-wrapped\\src\\containers\\profile\\index.jsx":"19","D:\\Projects\\git\\github-wrapped\\src\\containers\\landing\\index.jsx":"20","D:\\Projects\\git\\github-wrapped\\src\\constants\\particles.js":"21","D:\\Projects\\git\\github-wrapped\\src\\components\\index.js":"22","D:\\Projects\\git\\github-wrapped\\src\\helpers\\index.js":"23","D:\\Projects\\git\\github-wrapped\\src\\components\\report\\index.jsx":"24","D:\\Projects\\git\\github-wrapped\\src\\helpers\\api.js":"25","D:\\Projects\\git\\github-wrapped\\src\\helpers\\useScript.js":"26","D:\\Projects\\git\\github-wrapped\\src\\components\\loader\\index.jsx":"27","D:\\Projects\\git\\github-wrapped\\src\\components\\landing-header\\index.jsx":"28","C:\\Users\\ishan\\Documents\\github-wrapped\\src\\index.js":"29","C:\\Users\\ishan\\Documents\\github-wrapped\\src\\App.js":"30","C:\\Users\\ishan\\Documents\\github-wrapped\\src\\reportWebVitals.js":"31","C:\\Users\\ishan\\Documents\\github-wrapped\\src\\containers\\index.js":"32","C:\\Users\\ishan\\Documents\\github-wrapped\\src\\containers\\landing\\index.jsx":"33","C:\\Users\\ishan\\Documents\\github-wrapped\\src\\containers\\profile\\index.jsx":"34","C:\\Users\\ishan\\Documents\\github-wrapped\\src\\constants\\particles.js":"35","C:\\Users\\ishan\\Documents\\github-wrapped\\src\\components\\index.js":"36","C:\\Users\\ishan\\Documents\\github-wrapped\\src\\helpers\\index.js":"37","C:\\Users\\ishan\\Documents\\github-wrapped\\src\\components\\report\\index.jsx":"38","C:\\Users\\ishan\\Documents\\github-wrapped\\src\\helpers\\useScript.js":"39","C:\\Users\\ishan\\Documents\\github-wrapped\\src\\helpers\\api.js":"40","C:\\Users\\ishan\\Documents\\github-wrapped\\src\\components\\loader\\index.jsx":"41","C:\\Users\\ishan\\Documents\\github-wrapped\\src\\components\\landing-header\\index.jsx":"42"},{"size":500,"mtime":1608919117782,"results":"43","hashOfConfig":"44"},{"size":484,"mtime":1608842186487,"results":"45","hashOfConfig":"44"},{"size":362,"mtime":1607194323451,"results":"46","hashOfConfig":"44"},{"size":106,"mtime":1608798026093,"results":"47","hashOfConfig":"44"},{"size":1958,"mtime":1608845692886,"results":"48","hashOfConfig":"44"},{"size":2008,"mtime":1608795842988,"results":"49","hashOfConfig":"44"},{"size":185,"mtime":1608850896802,"results":"50","hashOfConfig":"44"},{"size":293,"mtime":1608797644836,"results":"51","hashOfConfig":"44"},{"size":1060,"mtime":1608914906729,"results":"52","hashOfConfig":"44"},{"size":6840,"mtime":1608921096605,"results":"53","hashOfConfig":"44"},{"size":2710,"mtime":1608914897891,"results":"54","hashOfConfig":"44"},{"size":216,"mtime":1608881660683,"results":"55","hashOfConfig":"44"},{"size":82,"mtime":1608883003631,"results":"56","hashOfConfig":"44"},{"size":413,"mtime":1608882992672,"results":"57","hashOfConfig":"44"},{"size":500,"mtime":1608919117782,"results":"58","hashOfConfig":"59"},{"size":362,"mtime":1607194323451,"results":"60","hashOfConfig":"59"},{"size":484,"mtime":1608842186487,"results":"61","hashOfConfig":"59"},{"size":106,"mtime":1608798026093,"results":"62","hashOfConfig":"59"},{"size":6717,"mtime":1608972929134,"results":"63","hashOfConfig":"59"},{"size":1958,"mtime":1608845692886,"results":"64","hashOfConfig":"59"},{"size":2008,"mtime":1608795842988,"results":"65","hashOfConfig":"59"},{"size":185,"mtime":1608850896802,"results":"66","hashOfConfig":"59"},{"size":82,"mtime":1608883003631,"results":"67","hashOfConfig":"59"},{"size":2710,"mtime":1608914897891,"results":"68","hashOfConfig":"59"},{"size":211,"mtime":1608921217033,"results":"69","hashOfConfig":"59"},{"size":413,"mtime":1608882992672,"results":"70","hashOfConfig":"59"},{"size":293,"mtime":1608797644836,"results":"71","hashOfConfig":"59"},{"size":1060,"mtime":1608914906729,"results":"72","hashOfConfig":"59"},{"size":517,"mtime":1609266330545,"results":"73","hashOfConfig":"74"},{"size":509,"mtime":1609266330528,"results":"75","hashOfConfig":"74"},{"size":375,"mtime":1609266330546,"results":"76","hashOfConfig":"74"},{"size":106,"mtime":1609266330540,"results":"77","hashOfConfig":"74"},{"size":1958,"mtime":1609267267287,"results":"78","hashOfConfig":"74"},{"size":6717,"mtime":1609266330541,"results":"79","hashOfConfig":"74"},{"size":2008,"mtime":1609266330539,"results":"80","hashOfConfig":"74"},{"size":185,"mtime":1609266330535,"results":"81","hashOfConfig":"74"},{"size":82,"mtime":1609266330544,"results":"82","hashOfConfig":"74"},{"size":2710,"mtime":1609266330537,"results":"83","hashOfConfig":"74"},{"size":413,"mtime":1609266330544,"results":"84","hashOfConfig":"74"},{"size":211,"mtime":1609266330543,"results":"85","hashOfConfig":"74"},{"size":293,"mtime":1609266330537,"results":"86","hashOfConfig":"74"},{"size":1173,"mtime":1609267351939,"results":"87","hashOfConfig":"74"},{"filePath":"88","messages":"89","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"j1i3eb",{"filePath":"90","messages":"91","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"92"},{"filePath":"93","messages":"94","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"92"},{"filePath":"95","messages":"96","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"92"},{"filePath":"97","messages":"98","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"92"},{"filePath":"99","messages":"100","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"92"},{"filePath":"101","messages":"102","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"92"},{"filePath":"103","messages":"104","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"92"},{"filePath":"105","messages":"106","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"107","usedDeprecatedRules":"92"},{"filePath":"108","messages":"109","errorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"110","messages":"111","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"112","usedDeprecatedRules":"92"},{"filePath":"113","messages":"114","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"92"},{"filePath":"115","messages":"116","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"92"},{"filePath":"117","messages":"118","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"92"},{"filePath":"119","messages":"120","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"yya0es",{"filePath":"121","messages":"122","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"123","messages":"124","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"125","messages":"126","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"127","messages":"128","errorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"129","messages":"130","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"131","messages":"132","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"133","messages":"134","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"135","messages":"136","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"137","messages":"138","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"139","messages":"140","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"141","messages":"142","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"143","messages":"144","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"145","messages":"146","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"147","messages":"148","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"5mei2i",{"filePath":"149","messages":"150","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"151","messages":"152","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"153","messages":"154","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"155","messages":"156","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"157","messages":"158","errorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"159","messages":"160","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"161","messages":"162","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"163","messages":"164","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"165","messages":"166","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"167","messages":"168","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"169","messages":"170","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"171","messages":"172","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"173","messages":"174","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},"D:\\Projects\\git\\year-in-review\\src\\index.js",[],"D:\\Projects\\git\\year-in-review\\src\\App.js",[],["175","176"],"D:\\Projects\\git\\year-in-review\\src\\reportWebVitals.js",[],"D:\\Projects\\git\\year-in-review\\src\\containers\\index.js",[],"D:\\Projects\\git\\year-in-review\\src\\containers\\landing\\index.jsx",[],"D:\\Projects\\git\\year-in-review\\src\\constants\\particles.js",[],"D:\\Projects\\git\\year-in-review\\src\\components\\index.js",[],"D:\\Projects\\git\\year-in-review\\src\\components\\loader\\index.jsx",[],"D:\\Projects\\git\\year-in-review\\src\\components\\landing-header\\index.jsx",["177"],"import React from \"react\";\r\n\r\nexport default function LandingHeader({\r\n inputHandler,\r\n keyUpHandler,\r\n username\r\n}) {\r\n return (\r\n <>\r\n
GitHub Wrapped \r\n 2020 \r\n\r\n \r\n This has been a challenging year for all of us.\r\n
\r\n\r\n \r\n Let's take a look back at all the contributions\r\n you as an individual\r\n \r\n made to the open-source community, during these unprecedented times.\r\n
\r\n\r\n \r\n \r\n Press 'Enter' to submit\r\n
\r\n >\r\n );\r\n}\r\n","D:\\Projects\\git\\year-in-review\\src\\containers\\profile\\index.jsx",["178","179","180","181","182"],"D:\\Projects\\git\\year-in-review\\src\\components\\report\\index.jsx",["183"],"import React from \"react\";\r\nimport \"./style.scss\";\r\n\r\nexport default function Report({ username, commits, stars, pr, issues, reportReference }) {\r\n const dataFormatter = (data) => {\r\n if (data > 1000) {\r\n return `${(data / 1000).toFixed(1)}k`;\r\n }\r\n return `${data}`;\r\n };\r\n\r\n return (\r\n \r\n
\r\n\r\n
\r\n
\r\n {username} 's\r\n \r\n
\r\n
2020 \r\n Year in review \r\n \r\n\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
{dataFormatter(commits)}
\r\n
Commits
\r\n
\r\n
\r\n\r\n
\r\n
\r\n
\r\n
{dataFormatter(stars)}
\r\n
Stars
\r\n
\r\n
\r\n
\r\n\r\n
\r\n
\r\n
\r\n
\r\n
{dataFormatter(pr)}
\r\n
Pull Requests
\r\n
\r\n
\r\n\r\n
\r\n
\r\n
\r\n
{dataFormatter(issues)}
\r\n
Issues
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n );\r\n}\r\n","D:\\Projects\\git\\year-in-review\\src\\helpers\\api.js",[],"D:\\Projects\\git\\year-in-review\\src\\helpers\\index.js",[],"D:\\Projects\\git\\year-in-review\\src\\helpers\\useScript.js",[],"D:\\Projects\\git\\github-wrapped\\src\\index.js",[],"D:\\Projects\\git\\github-wrapped\\src\\reportWebVitals.js",[],"D:\\Projects\\git\\github-wrapped\\src\\App.js",[],"D:\\Projects\\git\\github-wrapped\\src\\containers\\index.js",[],"D:\\Projects\\git\\github-wrapped\\src\\containers\\profile\\index.jsx",["184","185","186","187","188"],"D:\\Projects\\git\\github-wrapped\\src\\containers\\landing\\index.jsx",[],"D:\\Projects\\git\\github-wrapped\\src\\constants\\particles.js",[],"D:\\Projects\\git\\github-wrapped\\src\\components\\index.js",[],"D:\\Projects\\git\\github-wrapped\\src\\helpers\\index.js",[],"D:\\Projects\\git\\github-wrapped\\src\\components\\report\\index.jsx",["189"],"D:\\Projects\\git\\github-wrapped\\src\\helpers\\api.js",[],"D:\\Projects\\git\\github-wrapped\\src\\helpers\\useScript.js",[],"D:\\Projects\\git\\github-wrapped\\src\\components\\loader\\index.jsx",[],"D:\\Projects\\git\\github-wrapped\\src\\components\\landing-header\\index.jsx",["190"],"C:\\Users\\ishan\\Documents\\github-wrapped\\src\\index.js",[],"C:\\Users\\ishan\\Documents\\github-wrapped\\src\\App.js",[],"C:\\Users\\ishan\\Documents\\github-wrapped\\src\\reportWebVitals.js",[],"C:\\Users\\ishan\\Documents\\github-wrapped\\src\\containers\\index.js",[],"C:\\Users\\ishan\\Documents\\github-wrapped\\src\\containers\\landing\\index.jsx",[],"C:\\Users\\ishan\\Documents\\github-wrapped\\src\\containers\\profile\\index.jsx",["191","192","193","194","195"],"C:\\Users\\ishan\\Documents\\github-wrapped\\src\\constants\\particles.js",[],"C:\\Users\\ishan\\Documents\\github-wrapped\\src\\components\\index.js",[],"C:\\Users\\ishan\\Documents\\github-wrapped\\src\\helpers\\index.js",[],"C:\\Users\\ishan\\Documents\\github-wrapped\\src\\components\\report\\index.jsx",["196"],"C:\\Users\\ishan\\Documents\\github-wrapped\\src\\helpers\\useScript.js",[],"C:\\Users\\ishan\\Documents\\github-wrapped\\src\\helpers\\api.js",[],"C:\\Users\\ishan\\Documents\\github-wrapped\\src\\components\\loader\\index.jsx",[],"C:\\Users\\ishan\\Documents\\github-wrapped\\src\\components\\landing-header\\index.jsx",["197"],{"ruleId":"198","replacedBy":"199"},{"ruleId":"200","replacedBy":"201"},{"ruleId":"202","severity":1,"message":"203","line":35,"column":36,"nodeType":"204","messageId":"205","endLine":35,"endColumn":38},{"ruleId":"202","severity":1,"message":"206","line":46,"column":26,"nodeType":"204","messageId":"205","endLine":46,"endColumn":28},{"ruleId":"207","severity":1,"message":"208","line":119,"column":6,"nodeType":"209","endLine":119,"endColumn":8,"suggestions":"210"},{"ruleId":"211","severity":1,"message":"212","line":141,"column":19,"nodeType":"213","endLine":141,"endColumn":34},{"ruleId":"211","severity":1,"message":"212","line":175,"column":17,"nodeType":"213","endLine":175,"endColumn":32},{"ruleId":"211","severity":1,"message":"212","line":207,"column":17,"nodeType":"213","endLine":207,"endColumn":32},{"ruleId":"214","severity":1,"message":"215","line":14,"column":7,"nodeType":"216","endLine":14,"endColumn":72},{"ruleId":"202","severity":1,"message":"203","line":45,"column":24,"nodeType":"204","messageId":"205","endLine":45,"endColumn":26},{"ruleId":"207","severity":1,"message":"208","line":112,"column":6,"nodeType":"209","endLine":112,"endColumn":8,"suggestions":"217"},{"ruleId":"211","severity":1,"message":"212","line":134,"column":19,"nodeType":"213","endLine":134,"endColumn":34},{"ruleId":"211","severity":1,"message":"212","line":168,"column":17,"nodeType":"213","endLine":168,"endColumn":32},{"ruleId":"211","severity":1,"message":"212","line":200,"column":17,"nodeType":"213","endLine":200,"endColumn":32},{"ruleId":"214","severity":1,"message":"215","line":14,"column":7,"nodeType":"216","endLine":14,"endColumn":72},{"ruleId":"202","severity":1,"message":"203","line":35,"column":36,"nodeType":"204","messageId":"205","endLine":35,"endColumn":38},{"ruleId":"202","severity":1,"message":"203","line":45,"column":24,"nodeType":"204","messageId":"205","endLine":45,"endColumn":26},{"ruleId":"207","severity":1,"message":"208","line":112,"column":6,"nodeType":"209","endLine":112,"endColumn":8,"suggestions":"218"},{"ruleId":"211","severity":1,"message":"212","line":134,"column":19,"nodeType":"213","endLine":134,"endColumn":34},{"ruleId":"211","severity":1,"message":"212","line":168,"column":17,"nodeType":"213","endLine":168,"endColumn":32},{"ruleId":"211","severity":1,"message":"212","line":200,"column":17,"nodeType":"213","endLine":200,"endColumn":32},{"ruleId":"214","severity":1,"message":"215","line":14,"column":7,"nodeType":"216","endLine":14,"endColumn":72},{"ruleId":"202","severity":1,"message":"203","line":37,"column":36,"nodeType":"204","messageId":"205","endLine":37,"endColumn":38},"no-native-reassign",["219"],"no-negated-in-lhs",["220"],"eqeqeq","Expected '===' and instead saw '=='.","BinaryExpression","unexpected","Expected '!==' and instead saw '!='.","react-hooks/exhaustive-deps","React Hook useEffect has a missing dependency: 'fetchReport'. Either include it or remove the dependency array.","ArrayExpression",["221"],"react/jsx-no-target-blank","Using target=\"_blank\" without rel=\"noreferrer\" is a security risk: see https://html.spec.whatwg.org/multipage/links.html#link-type-noopener","JSXAttribute","jsx-a11y/alt-text","img elements must have an alt prop, either with meaningful text, or an empty string for decorative images.","JSXOpeningElement",["222"],["223"],"no-global-assign","no-unsafe-negation",{"desc":"224","fix":"225"},{"desc":"224","fix":"226"},{"desc":"224","fix":"227"},"Update the dependencies array to be: [fetchReport]",{"range":"228","text":"229"},{"range":"230","text":"229"},{"range":"231","text":"229"},[3039,3041],"[fetchReport]",[2927,2929],[2927,2929]]
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | .vercel
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Ishan Sharma
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Generate your report
5 |
6 |
7 |
8 | #### Why do I exist?
9 | ### 2020 has been a challenging year for all of us.
10 |
11 | As the year is ending, we thought to take a look back at all the contributions **you** as an individual
12 | made to the open-source community, during these unprecedented times.
13 |
14 | Also, since Spotify, Snapchat and other mass platforms have their own way of year wrap up, **why not us?**
15 |
16 | githubwrapped.tech
17 |
18 |
19 |
20 | ### Reports
21 |
22 | This web app deployed at [githubwrapped.tech](https://githubwrapped.tech) will help you to generate yearly reports that contains your stats from the beginning of this year.
23 |
24 | Including number of commits, stars etc.
25 |
26 |
27 |
28 | You can also save your report as an image using the 'Save Report' button and share it with the community or keep it as a memorandum :)
29 |
30 |
31 |
32 | #### Project Structure
33 |
34 |
35 | This project uses [React.Js](https://reactjs.org) and the contents are bundled using webpack.
36 |
37 | The contents in this project follow the following structure.
38 |
39 | ```
40 | ├───api
41 | ├───public
42 | │ └───assets
43 | └───src
44 | ├───components
45 | ├───constants
46 | ├───containers
47 | └───helpers
48 | ```
49 |
50 | Each individual component and container, follows this structure
51 | ```
52 | section
53 | ├──index.jsx (main entry point)
54 | └──style.scss
55 | ```
56 |
57 | ### Tweet Bot
58 |
59 | The celebrations doesn't end here! We also our own cute octo-bot on twitter [@GitHubWrapped](https://twitter.com/GitHubWrapped).
60 |
61 |
62 |
63 |
64 |
65 | Octobot stays awake all day/night to like & retweet all your tweets that use the hashtag #GitHubWrapped.
66 |
67 | ## Setup
68 |
69 | ##### Clone the repository
70 |
71 | ```bash
72 | git clone https://github.com/ishandeveloper/github-wrapped.git
73 | ```
74 |
75 | ##### Move to the desired folder
76 |
77 | ```bash
78 | cd \github-wrapped
79 | ```
80 |
81 | ##### To install the dependencies, simply write
82 |
83 | ```bash
84 | yarn install
85 | ```
86 |
87 | ##### To run the app, simply write
88 |
89 | ```bash
90 | yarn start
91 | ```
92 |
93 | ### Maintainers ✨
94 |
95 |
103 |
104 | [](https://github.com/ishandeveloper)
105 |
--------------------------------------------------------------------------------
/components/Button/Button.jsx:
--------------------------------------------------------------------------------
1 | // Stylesheets
2 | import Image from "next/image";
3 | import styles from "./Button.module.scss";
4 |
5 | const Button = ({
6 | startIcon = null,
7 | endIcon = null,
8 | label,
9 | onClick,
10 | ...props
11 | }) => {
12 | return (
13 |
14 | {startIcon !== null && (
15 |
22 | )}
23 |
24 | {label}
25 |
26 | {endIcon !== null && (
27 |
34 | )}
35 |
36 | );
37 | };
38 |
39 | export default Button;
40 |
--------------------------------------------------------------------------------
/components/Button/Button.module.scss:
--------------------------------------------------------------------------------
1 | .button {
2 | background: var(--color-primary);
3 | color: #ffffff;
4 | display: flex;
5 | align-items: center;
6 | justify-content: space-evenly;
7 | padding: 0.5rem 1rem;
8 | border: none;
9 | border-radius: 2px;
10 | box-shadow: 2px 4px 8px 0px rgba(0, 0, 0, 0.15);
11 | filter: brightness(1);
12 | cursor: pointer;
13 | z-index: 2;
14 | transition: all ease-in-out 250ms;
15 | }
16 |
17 | .button:disabled {
18 | cursor: not-allowed;
19 | background: #444444;
20 | }
21 |
22 | .text {
23 | font-weight: 400;
24 | font-size: 0.9rem;
25 | padding-right: 0.5rem;
26 | }
27 |
28 | .logo {
29 | height: 26px;
30 | height: 26px;
31 | margin-right: 1rem;
32 | &:hover {
33 | background: #444444;
34 | box-shadow: 2px 4px 8px 0px rgba(0, 0, 0, 0.15);
35 | }
36 | }
37 |
38 | .svg {
39 | height: 26px;
40 | width: 26px;
41 | fill: #ffffff;
42 | }
43 |
--------------------------------------------------------------------------------
/components/ContributionsGraph/ContributionsGraph.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect } from "react";
2 |
3 | import { useFrame, Canvas } from "@react-three/fiber";
4 |
5 | import TrophyModel from "../TrophyModel/TrophyModel";
6 | import * as THREE from "three";
7 | import GraphLoading from "./GraphLoading";
8 |
9 | const ContributionsGraph = ({
10 | width = 720,
11 | height = 400,
12 | className,
13 | data,
14 | username,
15 | setScene,
16 | }) => {
17 | if (data === null) return ;
18 |
19 | return (
20 |
21 |
22 |
27 |
28 | );
29 | };
30 |
31 | const Lights = () => {
32 | const lightRef = useRef(null);
33 | const light2Ref = useRef(null);
34 | const light3Ref = useRef(null);
35 | const light4Ref = useRef(null);
36 |
37 | useFrame((state) => {
38 | let pos = state.camera.position;
39 | lightRef.current.position.copy(pos);
40 | light2Ref.current.position.copy(new THREE.Vector3(pos.x + 3, pos.y, pos.z));
41 | light3Ref.current.position.copy(new THREE.Vector3(pos.x - 3, pos.y, pos.z));
42 | light4Ref.current.position.copy(
43 | new THREE.Vector3(pos.x, pos.y - 5, pos.z - 1)
44 | );
45 | });
46 |
47 | return (
48 | <>
49 |
55 |
56 |
62 |
63 |
69 |
70 |
76 | >
77 | );
78 | };
79 |
80 | export default ContributionsGraph;
81 |
--------------------------------------------------------------------------------
/components/ContributionsGraph/GraphLoading.jsx:
--------------------------------------------------------------------------------
1 | const GraphLoading = ({ className }) => {
2 | const commits = new Array(280).fill(0);
3 |
4 | return (
5 |
6 |
7 | {commits.map((commit, index) => (
8 |
9 | ))}
10 |
11 |
12 | );
13 | };
14 |
15 | export default GraphLoading;
16 |
--------------------------------------------------------------------------------
/components/GithubOAuthButton/GithubOAuthButton.jsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 | import Button from "../Button/Button";
3 |
4 | const GitHubOAuthButton = ({ isLinked = false }) => {
5 | const router = useRouter();
6 |
7 | return (
8 | router.push("/api/oauth/login")}
11 | startIcon={isLinked ? "/icons/check.svg" : "/icons/github.svg"}
12 | disabled={isLinked}
13 | />
14 | );
15 | };
16 |
17 | export default GitHubOAuthButton;
18 |
--------------------------------------------------------------------------------
/components/OGHead/OGHead.jsx:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 |
3 | const OGHead = ({ username }) => {
4 | return (
5 |
6 | {username}'s GitHub Report 2022
7 |
11 |
12 |
16 |
17 |
21 |
25 |
26 |
27 |
28 |
29 |
33 |
37 |
41 |
45 |
49 |
50 | );
51 | };
52 |
53 | export default OGHead;
54 |
--------------------------------------------------------------------------------
/components/ReportCard/ReportCard.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { statsFormatter } from "../../helpers/statsFormatter";
4 |
5 | const ReportCard = ({ commits, stars, issues, pr, username, avatar, name }) => {
6 | return (
7 |
15 |
16 |
17 |
27 |
36 |
37 | commits
38 |
39 |
40 |
50 |
51 | {statsFormatter(commits)}
52 |
53 |
54 |
55 |
56 |
66 |
75 |
76 | stars
77 |
78 |
79 |
89 |
90 | {statsFormatter(stars)}
91 |
92 |
93 |
94 |
95 |
105 |
114 |
115 | issues
116 |
117 |
118 |
128 |
129 | {statsFormatter(issues)}
130 |
131 |
132 |
133 |
134 |
144 |
153 |
154 | pull requests
155 |
156 |
157 |
167 |
168 | {statsFormatter(pr)}
169 |
170 |
171 |
172 |
180 |
181 |
190 |
191 | @{username}
192 |
193 |
194 |
204 |
205 | {name}
206 |
207 |
208 |
209 |
219 |
220 |
230 |
231 | githubwrapped.tech
232 |
233 |
234 |
244 |
245 | 2022 GitHub Report
246 |
247 |
248 |
253 |
254 |
255 |
259 |
265 |
269 |
270 |
271 |
272 |
273 |
280 |
281 |
282 | );
283 | };
284 |
285 | export default ReportCard;
286 |
--------------------------------------------------------------------------------
/components/ReportCard/ReportCardLoading.jsx:
--------------------------------------------------------------------------------
1 | const ReportCardLoading = () => {
2 | return (
3 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
25 |
34 |
43 |
44 |
53 |
54 | githubwrapped.tech
55 |
56 |
57 |
66 |
67 | 2022 GitHub Report
68 |
69 |
70 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | );
82 | };
83 |
84 | export default ReportCardLoading;
85 |
--------------------------------------------------------------------------------
/components/ShareOnLinkedin/ShareOnLinkedIn.jsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | import styles from "./ShareOnLinkedIn.module.scss";
4 |
5 | const ShareOnLinkedIn = ({ username, commits, pr }) => {
6 | return (
7 |
13 |
19 | Share on LinkedIn
20 |
21 | );
22 | };
23 |
24 | export default ShareOnLinkedIn;
25 |
--------------------------------------------------------------------------------
/components/ShareOnLinkedin/ShareOnLinkedIn.module.scss:
--------------------------------------------------------------------------------
1 | .linkedinBtn {
2 | background: #0077B7;
3 | color: #ffffff;
4 | display: flex;
5 | align-items: center;
6 | justify-content: space-evenly;
7 | padding: 0.5rem 1rem;
8 | border: none;
9 | border-radius: 2px;
10 | box-shadow: 2px 4px 8px 0px rgba(0, 0, 0, 0.15);
11 | filter: brightness(1);
12 | cursor: pointer;
13 | z-index: 2;
14 | transition: all ease-in-out 250ms;
15 | width: max-content;
16 | text-decoration: none;
17 |
18 | span {
19 | margin-left: 0.5rem;
20 | font-size: 1rem;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/components/ShareOnTwitter/ShareOnTwitter.jsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | import styles from "./ShareOnTwitter.module.scss";
4 |
5 | const ShareOnTwitter = ({ username, commits, pr }) => {
6 | return (
7 |
13 |
19 | Share on Twitter
20 |
21 | );
22 | };
23 |
24 | export default ShareOnTwitter;
25 |
--------------------------------------------------------------------------------
/components/ShareOnTwitter/ShareOnTwitter.module.scss:
--------------------------------------------------------------------------------
1 | .twitterBtn {
2 | background: #1d9bf0;
3 |
4 | color: #ffffff;
5 | display: flex;
6 | align-items: center;
7 | justify-content: space-evenly;
8 | padding: 0.5rem 1rem;
9 | border: none;
10 | border-radius: 2px;
11 | box-shadow: 2px 4px 8px 0px rgba(0, 0, 0, 0.15);
12 | filter: brightness(1);
13 | cursor: pointer;
14 | z-index: 2;
15 | transition: all ease-in-out 250ms;
16 | width: max-content;
17 | text-decoration: none;
18 |
19 | span {
20 | margin-left: 0.5rem;
21 | font-size: 1rem;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/components/TrophyModel/TrophyModel.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect } from "react";
2 | import * as THREE from "three";
3 | import { useThree } from "@react-three/fiber";
4 | import wrappedFont from "three/examples/fonts/droid/droid_sans_bold.typeface.json";
5 | import { graphThreeEntry } from "./graph_three_entry";
6 |
7 | const BASE_COLOR = 0x113692;
8 | const BAR_COLORS = [0xf0edff, 0x0097ba, 0x007abc, 0x005aaf, 0x001668];
9 | const ROUGHNESS = 0.4;
10 | const MAX_BAR_Z = 0.5;
11 | const MIN_BAR_Z = 0.025;
12 |
13 | const TrophyModel = ({ data, entity, setScene }) => {
14 | const width = Math.ceil(data.length / 7) / 7;
15 | const objectRef = useRef();
16 | const baseGeometryRef = useRef();
17 |
18 | const { scene } = useThree();
19 |
20 | useEffect(() => {
21 | if (scene !== null) setScene(scene);
22 | }, [scene, setScene]);
23 |
24 | const getBase = () => {
25 | return (
26 |
30 |
31 |
37 |
38 | );
39 | };
40 |
41 | const getBars = () => {
42 | const x0 = -1 / 7;
43 | const z0 = -6 / 7;
44 |
45 | const maxCount = data.reduce((prev, curr) => {
46 | return curr.count > prev ? curr.count : prev;
47 | }, 0);
48 |
49 | return data.map((day, idx) => {
50 | if (!day.count) {
51 | return null;
52 | }
53 |
54 | const week = Math.floor(idx / 7);
55 | const dayOfWeek = idx % 7;
56 | const height =
57 | MIN_BAR_Z + (MAX_BAR_Z - MIN_BAR_Z) * (day.count / maxCount);
58 | const pos = new THREE.Vector3(
59 | x0 + (week * 1) / 7,
60 | 0.5 * height,
61 | z0 + (dayOfWeek * 1) / 7
62 | );
63 |
64 | return (
65 |
66 |
67 |
74 |
75 | );
76 | });
77 | };
78 |
79 | const getLabelText = () => {
80 | const truncatedName =
81 | entity.length < 23 ? entity : entity.slice(0, 20) + "...";
82 | return `@${truncatedName}`;
83 | };
84 |
85 | const getLabel = () => {
86 | const font = new THREE.FontLoader().parse(wrappedFont);
87 |
88 | const textOptions = {
89 | font,
90 | size: 0.175,
91 | height: 0.15,
92 | };
93 |
94 | const wrappedTextOptions = {
95 | font,
96 | size: 0.125,
97 | height: 0.1,
98 | };
99 |
100 | return (
101 | <>
102 |
103 |
107 |
113 |
114 |
115 |
116 |
120 |
126 |
127 | >
128 | );
129 | };
130 |
131 | const onGroupRender = (group) => {
132 | const pivot = new THREE.Group();
133 | scene.add(pivot);
134 | pivot.add(group);
135 | group.position.set(-width / 2, 0, 0);
136 |
137 | graphThreeEntry(pivot, baseGeometryRef.current);
138 | };
139 |
140 | return data ? (
141 |
147 | {getBase()}
148 | {getBars()}
149 | {getLabel()}
150 |
151 | ) : null;
152 | };
153 |
154 | export default TrophyModel;
155 |
--------------------------------------------------------------------------------
/components/TrophyModel/graph_three_entry.js:
--------------------------------------------------------------------------------
1 | import gsap from "gsap";
2 |
3 | let mouseDown = false,
4 | mouseX = 0,
5 | mouseY = 0,
6 | pivotElement,
7 | baseElement;
8 |
9 | export const graphThreeEntry = (pivot, base) => {
10 | pivotElement = pivot;
11 | baseElement = base;
12 | initLanding();
13 | addMouseHandler(document.querySelector("canvas"));
14 | };
15 |
16 | const initLanding = (base) => {
17 | const timeline = gsap.timeline();
18 |
19 | if (pivotElement) {
20 | pivotElement.rotation.y = 0.25;
21 | }
22 |
23 | timeline.timeScale(2);
24 |
25 | if (baseElement && pivotElement) {
26 | timeline
27 | .to(
28 | pivotElement.rotation,
29 | {
30 | x: 0,
31 | y: -0.25,
32 | z: 0,
33 | },
34 | "+=0.5"
35 | )
36 | .to(
37 | pivotElement.rotation,
38 | {
39 | x: 0,
40 | y: 0,
41 | z: 0,
42 | },
43 | "+=0.5"
44 | )
45 | .to(
46 | pivotElement.scale,
47 | {
48 | x: 1,
49 | y: 1,
50 | z: 1,
51 | },
52 | "-=1"
53 | );
54 | }
55 | };
56 |
57 | const addMouseHandler = (canvas) => {
58 | canvas.addEventListener("mousemove", (e) => onMouseMove(e), false);
59 | canvas.addEventListener("mousedown", (e) => onMouseDown(e), false);
60 | canvas.addEventListener("mouseup", (e) => onMouseUp(e), false);
61 | canvas.addEventListener("wheel", (e) => onMouseWheel(e), false);
62 | };
63 |
64 | const onMouseMove = (evt) => {
65 | if (!mouseDown) {
66 | return;
67 | }
68 |
69 | evt.preventDefault();
70 |
71 | const deltaX = evt.clientX - mouseX;
72 | const deltaY = evt.clientY - mouseY;
73 |
74 | mouseX = evt.clientX;
75 | mouseY = evt.clientY;
76 | rotateScene(deltaX, deltaY);
77 | };
78 |
79 | const onMouseDown = (evt) => {
80 | evt.preventDefault();
81 |
82 | mouseDown = true;
83 | mouseX = evt.clientX;
84 | mouseY = evt.clientY;
85 | };
86 |
87 | const onMouseUp = (evt) => {
88 | evt.preventDefault();
89 |
90 | mouseDown = false;
91 | };
92 |
93 | const onMouseWheel = (evt) => {
94 | evt.preventDefault();
95 | zoomScene(evt.deltaY);
96 | };
97 | const rotateScene = (deltaX, deltaY) => {
98 | if (deltaX < 0 && pivotElement.rotation.y > -0.75) {
99 | pivotElement.rotation.y += deltaX / 100;
100 | } else if (deltaX > 0 && pivotElement.rotation.y < 0.75) {
101 | pivotElement.rotation.y += deltaX / 100;
102 | }
103 |
104 | if (deltaY < 0 && pivotElement.rotation.x > -0.75) {
105 | pivotElement.rotation.x += deltaY / 100;
106 | } else if (deltaY > 0 && pivotElement.rotation.x < 0.75) {
107 | pivotElement.rotation.x += deltaY / 100;
108 | }
109 | };
110 |
111 | const zoomScene = (deltaY) => {
112 | if (deltaY < 0 && pivotElement.scale.x > 0.75) {
113 | pivotElement.scale.x += deltaY / 100;
114 | pivotElement.scale.y += deltaY / 100;
115 | pivotElement.scale.z += deltaY / 100;
116 | } else if (deltaY > 0 && pivotElement.scale.x < 1.5) {
117 | pivotElement.scale.x += deltaY / 100;
118 | pivotElement.scale.y += deltaY / 100;
119 | pivotElement.scale.z += deltaY / 100;
120 | }
121 | };
122 |
--------------------------------------------------------------------------------
/constants/particleOptions.js:
--------------------------------------------------------------------------------
1 | export const particleOptions = {
2 | autoPlay: true,
3 | background: {
4 | color: {
5 | value: "#ffffff",
6 | },
7 | },
8 | fullScreen: {
9 | enable: true,
10 | zIndex: -1,
11 | },
12 | detectRetina: true,
13 | duration: 0,
14 | fpsLimit: 60,
15 |
16 | particles: {
17 | bounce: {
18 | horizontal: {
19 | random: {
20 | enable: false,
21 | minimumValue: 0.1,
22 | },
23 | value: 0,
24 | },
25 | vertical: {
26 | random: {
27 | enable: false,
28 | minimumValue: 0.1,
29 | },
30 | value: 0,
31 | },
32 | },
33 | collisions: {
34 | bounce: {
35 | horizontal: {
36 | random: {
37 | enable: false,
38 | minimumValue: 0.1,
39 | },
40 | value: 1,
41 | },
42 | vertical: {
43 | random: {
44 | enable: false,
45 | minimumValue: 0.1,
46 | },
47 | value: 1,
48 | },
49 | },
50 | enable: false,
51 | mode: "bounce",
52 | overlap: {
53 | enable: true,
54 | retries: 0,
55 | },
56 | },
57 | color: {
58 | value: ["#1E00FF", "#FF0061", "#E1FF00", "#00FF9E"],
59 | animation: {
60 | h: {
61 | count: 0,
62 | enable: false,
63 | offset: 0,
64 | speed: 30,
65 | sync: true,
66 | },
67 | s: {
68 | count: 0,
69 | enable: false,
70 | offset: 0,
71 | speed: 1,
72 | sync: true,
73 | },
74 | l: {
75 | count: 0,
76 | enable: false,
77 | offset: 0,
78 | speed: 1,
79 | sync: true,
80 | },
81 | },
82 | },
83 | destroy: {
84 | mode: "none",
85 | split: {
86 | count: 1,
87 | factor: {
88 | random: {
89 | enable: false,
90 | minimumValue: 0,
91 | },
92 | value: 3,
93 | },
94 | rate: {
95 | random: {
96 | enable: false,
97 | minimumValue: 0,
98 | },
99 | value: {
100 | min: 9,
101 | max: 10,
102 | },
103 | },
104 | sizeOffset: true,
105 | },
106 | },
107 | gradient: [],
108 | groups: {},
109 | life: {
110 | count: 0,
111 | delay: {
112 | random: {
113 | enable: false,
114 | minimumValue: 0,
115 | },
116 | value: 0,
117 | sync: false,
118 | },
119 | duration: {
120 | random: {
121 | enable: false,
122 | minimumValue: 0.0001,
123 | },
124 | value: 0,
125 | sync: false,
126 | },
127 | },
128 |
129 | move: {
130 | angle: {
131 | offset: 0,
132 | value: 90,
133 | },
134 | attract: {
135 | distance: 200,
136 | enable: false,
137 | rotate: {
138 | x: 3000,
139 | y: 3000,
140 | },
141 | },
142 | decay: 0.1,
143 | distance: {},
144 | direction: "top",
145 | drift: 0,
146 | enable: true,
147 | gravity: {
148 | acceleration: 3,
149 | enable: true,
150 | inverse: true,
151 | maxSpeed: 200,
152 | },
153 | path: {
154 | clamp: true,
155 | delay: {
156 | random: {
157 | enable: false,
158 | minimumValue: 0,
159 | },
160 | value: 0,
161 | },
162 | enable: false,
163 | options: {},
164 | },
165 | outModes: {
166 | default: "destroy",
167 | bottom: "bounce",
168 | left: "destroy",
169 | right: "destroy",
170 | top: "none",
171 | },
172 | random: false,
173 | size: false,
174 | speed: {
175 | min: 50,
176 | max: 150,
177 | },
178 | spin: {
179 | acceleration: 0,
180 | enable: false,
181 | },
182 | straight: false,
183 | trail: {
184 | enable: false,
185 | length: 10,
186 | fillColor: {
187 | value: "#000000",
188 | },
189 | },
190 | vibrate: false,
191 | warp: false,
192 | },
193 | number: {
194 | density: {
195 | enable: false,
196 | area: 800,
197 | factor: 1000,
198 | },
199 | limit: 300,
200 | value: 0,
201 | },
202 | opacity: {
203 | random: {
204 | enable: false,
205 | minimumValue: 0.1,
206 | },
207 | value: 1,
208 | animation: {
209 | count: 0,
210 | enable: false,
211 | speed: 0.3,
212 | sync: true,
213 | destroy: "min",
214 | startValue: "max",
215 | },
216 | },
217 | orbit: {
218 | animation: {
219 | count: 0,
220 | enable: false,
221 | speed: 1,
222 | sync: false,
223 | },
224 | enable: false,
225 | opacity: 1,
226 | rotation: {
227 | random: {
228 | enable: false,
229 | minimumValue: 0,
230 | },
231 | value: 45,
232 | },
233 | width: 1,
234 | },
235 | reduceDuplicates: false,
236 | repulse: {
237 | random: {
238 | enable: false,
239 | minimumValue: 0,
240 | },
241 | value: 0,
242 | enabled: false,
243 | distance: 1,
244 | duration: 1,
245 | factor: 1,
246 | speed: 1,
247 | },
248 | roll: {
249 | darken: {
250 | enable: true,
251 | value: 30,
252 | },
253 | enable: true,
254 | enlighten: {
255 | enable: true,
256 | value: 30,
257 | },
258 | mode: "vertical",
259 | speed: {
260 | min: 15,
261 | max: 25,
262 | },
263 | },
264 | rotate: {
265 | random: {
266 | enable: false,
267 | minimumValue: 0,
268 | },
269 | value: {
270 | min: 0,
271 | max: 360,
272 | },
273 | animation: {
274 | enable: true,
275 | speed: 60,
276 | sync: false,
277 | },
278 | direction: "random",
279 | path: false,
280 | },
281 | shadow: {
282 | blur: 0,
283 | color: {
284 | value: "#000000",
285 | },
286 | enable: false,
287 | offset: {
288 | x: 0,
289 | y: 0,
290 | },
291 | },
292 | shape: {
293 | options: {
294 | polygon: [
295 | {
296 | sides: 5,
297 | },
298 | {
299 | sides: 6,
300 | },
301 | ],
302 | character: [
303 | {
304 | value: ["💩", "🤡", "🍀", "🍙"],
305 | },
306 | ],
307 | },
308 | type: [
309 | "circle",
310 | "square",
311 | "polygon",
312 | "character",
313 | "character",
314 | "character",
315 | ],
316 | },
317 | size: {
318 | random: {
319 | enable: false,
320 | minimumValue: 1,
321 | },
322 | value: 3,
323 | animation: {
324 | count: 0,
325 | enable: false,
326 | speed: 5,
327 | sync: false,
328 | destroy: "none",
329 | startValue: "random",
330 | },
331 | },
332 | stroke: {
333 | width: 0,
334 | },
335 | tilt: {
336 | random: {
337 | enable: false,
338 | minimumValue: 0,
339 | },
340 | value: {
341 | min: 0,
342 | max: 360,
343 | },
344 | animation: {
345 | enable: true,
346 | speed: 60,
347 | sync: false,
348 | },
349 | direction: "random",
350 | enable: true,
351 | },
352 | twinkle: {
353 | lines: {
354 | enable: false,
355 | frequency: 0.05,
356 | opacity: 1,
357 | },
358 | particles: {
359 | enable: false,
360 | frequency: 0.05,
361 | opacity: 1,
362 | },
363 | },
364 | wobble: {
365 | distance: 30,
366 | enable: true,
367 | speed: {
368 | min: -15,
369 | max: 15,
370 | },
371 | },
372 | zIndex: {
373 | random: {
374 | enable: false,
375 | minimumValue: 0,
376 | },
377 | value: 0,
378 | opacityRate: 1,
379 | sizeRate: 1,
380 | velocityRate: 1,
381 | },
382 | },
383 | pauseOnBlur: true,
384 | pauseOnOutsideViewport: true,
385 | responsive: [],
386 | themes: [],
387 | zLayers: 100,
388 | emitters: {
389 | autoPlay: true,
390 | fill: true,
391 | life: {
392 | wait: false,
393 | },
394 | rate: {
395 | quantity: 2,
396 | delay: 0.5,
397 | },
398 | shape: "square",
399 | startCount: 0,
400 | size: {
401 | mode: "percent",
402 | height: 0,
403 | width: 0,
404 | },
405 | position: {
406 | x: 50,
407 | y: 100,
408 | },
409 | },
410 | };
411 |
--------------------------------------------------------------------------------
/containers/Share/Share.jsx:
--------------------------------------------------------------------------------
1 | import ShareOnLinkedIn from "../../components/ShareOnLinkedin/ShareOnLinkedIn";
2 | import ShareOnTwitter from "../../components/ShareOnTwitter/ShareOnTwitter";
3 |
4 | const ShareContainer = ({ username, commits, pr }) => {
5 | const { heading, message } = getMessage(commits, pr);
6 |
7 | return (
8 | <>
9 | {heading}
10 | {message}
11 |
12 |
13 |
14 |
15 | >
16 | );
17 | };
18 |
19 | const getMessage = (commits, pr) => {
20 | const totalContributions = commits + pr;
21 |
22 | if (totalContributions > 800) {
23 | return {
24 | heading: "Wow! You are a rockstar! ✨",
25 | message: `Making over ${commits} commits & ${pr} PRs in a single year, is no easy feat! Let's celebrate your victory by sharing it with the world!`,
26 | };
27 | } else if (totalContributions > 400) {
28 | return {
29 | heading: "Damn! Nice work!",
30 | message: `Making over ${commits} commits & ${pr} PRs in a single year, is pretty cool! Let's celebrate your victory by sharing it with the world!`,
31 | };
32 | } else if (totalContributions > 100) {
33 | return {
34 | heading: "Quality > Quantity!",
35 | message: `You made over ${commits} commits & ${pr} PRs in a single year, which makes you a valuable contributor to the open-source community! Let's celebrate your victory by sharing it with the world!`,
36 | };
37 | } else {
38 | return {
39 | heading: "Well done! :)",
40 | message: `Everybody starts somewhere. If 2022 was your first year in contributing to open source, please give yourself a pat on the back! Here's to more such contributions in the upcoming future! 🥂`,
41 | };
42 | }
43 | };
44 | export default ShareContainer;
45 |
--------------------------------------------------------------------------------
/helpers/getUserReport.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | export const getUserReport = async (username, token) => {
4 | if (!token)
5 | return axios.get(`/api/stats?username=${username}`).then((res) => res.data);
6 |
7 | return axios
8 | .get(`/api/stats?username=${username}&token=${token}`)
9 | .then((res) => res.data);
10 | };
11 |
--------------------------------------------------------------------------------
/helpers/sceneToSTL.js:
--------------------------------------------------------------------------------
1 | import GLTFExporter from "three-gltf-exporter";
2 |
3 | export const exportSceneToSTL = (scene, username) => {
4 | const exporter = new GLTFExporter();
5 |
6 | exporter.parse(scene, (result) => {
7 | if (result instanceof ArrayBuffer) {
8 | saveArrayBuffer(result, `${username}-report.glb`);
9 | } else {
10 | const output = JSON.stringify(result, null, 2);
11 | saveString(output, `${username}-report.gltf`);
12 | }
13 | });
14 | };
15 |
16 | function save(blob, filename) {
17 | const link = document.createElement("a");
18 | link.href = URL.createObjectURL(blob);
19 | link.download = filename;
20 | link.click();
21 | }
22 |
23 | function saveString(text, filename) {
24 | save(new Blob([text], { type: "text/plain" }), filename);
25 | }
26 |
27 | function saveArrayBuffer(buffer, filename) {
28 | save(new Blob([buffer], { type: "application/octet-stream" }), filename);
29 | }
30 |
--------------------------------------------------------------------------------
/helpers/statsFormatter.js:
--------------------------------------------------------------------------------
1 | export const statsFormatter = (num) => {
2 | const data = parseInt(num);
3 | if (data > 1000) {
4 | return `${(data / 1000).toFixed(1)}k`;
5 | }
6 | return `${data}`;
7 | };
8 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | eslint: {
5 | ignoreDuringBuilds: true,
6 | },
7 | };
8 |
9 | module.exports = nextConfig;
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "githubwrapped",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@react-three/drei": "^8.0.0",
13 | "@react-three/fiber": "^7.0.24",
14 | "@vercel/og": "^0.0.21",
15 | "axios": "^1.2.1",
16 | "dotenv": "^8.2.0",
17 | "eslint": "8.29.0",
18 | "eslint-config-next": "13.0.6",
19 | "gsap": "^3.8.0",
20 | "isomorphic-fetch": "^3.0.0",
21 | "jssoup": "^0.0.15",
22 | "next": "13.0.6",
23 | "nextjs-google-analytics": "^2.2.2",
24 | "react": "18.2.0",
25 | "react-dom": "18.2.0",
26 | "react-github-btn": "^1.4.0",
27 | "react-tsparticles": "1.36.0",
28 | "sass": "^1.56.2",
29 | "three": "0.127.0",
30 | "three-gltf-exporter": "^0.0.1"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/pages/[username]/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 |
3 | // Hooks / Utils
4 | import { getUserReport } from "../../helpers/getUserReport";
5 | import { useRouter } from "next/router";
6 |
7 | // Components
8 | import Image from "next/image";
9 |
10 | import GitHubOAuthButton from "../../components/GithubOAuthButton/GithubOAuthButton";
11 | import ContributionsGraph from "../../components/ContributionsGraph/ContributionsGraph";
12 | import ReportCardLoading from "../../components/ReportCard/ReportCardLoading";
13 | import ReportCard from "../../components/ReportCard/ReportCard";
14 | import OGHead from "../../components/OGHead/OGHead";
15 |
16 | // Styles
17 | import styles from "../../styles/Report.module.scss";
18 | import { exportSceneToSTL } from "../../helpers/sceneToSTL";
19 | import Button from "../../components/Button/Button";
20 | import ShareOnTwitter from "../../components/ShareOnTwitter/ShareOnTwitter";
21 | import ShareContainer from "../../containers/Share/Share";
22 |
23 | export async function getServerSideProps(context) {
24 | const { username } = context.params;
25 | const token = context.query.token ?? null;
26 | return {
27 | props: {
28 | username,
29 | token,
30 | },
31 | };
32 | }
33 | const ReportWrapper = ({ username, token, data }) => {
34 | // Hooks
35 | const { query, push } = useRouter();
36 |
37 | // State
38 | const [reportData, setReportData] = useState(null);
39 | const [isLoading, setIsLoading] = useState(false);
40 | const [errorMessage, setErrorMessage] = useState(null);
41 | const [scene, setScene] = useState(null);
42 |
43 | const fetchUserReport = () => {
44 | setIsLoading(true);
45 | getUserReport(username, token)
46 | .then((response) => setReportData(response["data"]))
47 | .catch((e) => setErrorMessage(e));
48 | };
49 |
50 | useEffect(() => {
51 | if (!isLoading && !reportData && !errorMessage && username !== null)
52 | fetchUserReport();
53 | // eslint-disable-next-line react-hooks/exhaustive-deps
54 | }, [errorMessage, reportData, isLoading, username]);
55 |
56 | const downloadSTL = () => exportSceneToSTL(scene, username);
57 |
58 | return (
59 |
60 |
61 | push("/")}>
62 |
68 |
#GitHubWrapped
69 |
70 |
71 |
72 |
73 |
81 |
82 |
83 |
84 | {token === null && (
85 |
Want to add your private contributions?
86 | )}
87 | {token !== null && (
88 |
Yay! You've linked your github account! 🎉
89 | )}
90 |
91 | Which means that all of your private contributions are now being
92 | included in the calculation.
93 |
94 |
95 |
96 |
97 |
98 |
99 |
Want to put this on your shelf?
100 |
Download the .STL file and print it out on your 3D printer.
101 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | Your 2022 GitHub Report
115 |
116 | {reportData === null ? (
117 |
118 | ) : (
119 |
128 | )}
129 |
130 |
131 |
132 | {reportData !== null && (
133 |
138 | )}
139 |
140 |
141 |
142 |
143 |
144 | *This project is neither maintained nor endorsed by GitHub.
145 |
146 |
147 | );
148 | };
149 |
150 | export default ReportWrapper;
151 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import GitHubButton from "react-github-btn";
2 | import { GoogleAnalytics } from "nextjs-google-analytics";
3 | import "../styles/globals.scss";
4 | import "../styles/landing.scss";
5 |
6 | function MyApp({ Component, pageProps }) {
7 | return (
8 | <>
9 |
10 |
11 |
12 |
19 | Star
20 |
21 |
22 | >
23 | );
24 | }
25 |
26 | export default MyApp;
27 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from "next/document";
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/pages/api/multitokenizer.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config();
2 |
3 | const multiTokenizer = async (func, params, token_no) => {
4 | try {
5 | const token = process.env[`TOKEN_${token_no + 1}`];
6 | const res = await func(params, token);
7 | return res;
8 | } catch (e) {
9 | if (token_no < process.env.TOKENS_COUNT) {
10 | // If there is an error, try with other tokens
11 | return multiTokenizer(func, params, token_no + 1);
12 | }
13 |
14 | // If exceeds the number of tokens, return 0 so that app does not crash
15 | return 0;
16 | }
17 | };
18 | module.exports = { multiTokenizer };
19 |
--------------------------------------------------------------------------------
/pages/api/oauth/login.js:
--------------------------------------------------------------------------------
1 | const CLIENT_ID = process.env.GH_CLIENT_ID;
2 | const REDIRECT_URL = process.env.GH_REDIRECT_URL;
3 |
4 | module.exports = async (req, res) => {
5 | return res.redirect(
6 | `https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URL}&scope=user,repo,read:org`
7 | );
8 | };
9 |
--------------------------------------------------------------------------------
/pages/api/oauth/redirect.js:
--------------------------------------------------------------------------------
1 | const axios = require("axios");
2 |
3 | const CLIENT_ID = process.env.GH_CLIENT_ID;
4 | const CLIENT_SECRET = process.env.GH_CLIENT_SECRET;
5 |
6 | const getAccessTokenFromCode = async (code) =>
7 | axios({
8 | method: "post",
9 | url: `https://github.com/login/oauth/access_token?client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&code=${code}`,
10 | headers: {
11 | accept: "application/json",
12 | },
13 | });
14 |
15 | const getUsernameFromToken = async (token) =>
16 | axios({
17 | method: "get",
18 | url: "https://api.github.com/user",
19 | headers: {
20 | accept: "application/json",
21 | Authorization: `Bearer ${token}`,
22 | },
23 | });
24 |
25 | module.exports = async (req, res) => {
26 | try {
27 | const accessToken = await getAccessTokenFromCode(req.query.code).then(
28 | (res) => res.data["access_token"]
29 | );
30 |
31 | const username = await getUsernameFromToken(accessToken).then(
32 | (res) => res.data["login"]
33 | );
34 |
35 | return res.redirect(`/${username}?token=${accessToken}`);
36 | } catch (err) {
37 | return res.redirect(`/?error=OAUTH_ERROR`);
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/pages/api/report.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/no-img-element */
2 | const { ImageResponse } = require("@vercel/og");
3 |
4 | import axios from "axios";
5 | import { statsFormatter } from "../../helpers/statsFormatter";
6 |
7 | export const config = {
8 | runtime: "experimental-edge",
9 | };
10 |
11 | const getRandomColor = () => {
12 | const colors = [
13 | "#c70dc9",
14 | "#fa279b",
15 | "#19ab4c",
16 | "#401b71",
17 | "#24757a",
18 | "#f53b57",
19 | "#f3f300",
20 | ];
21 |
22 | const randomColor = colors[Math.floor(Math.random() * colors.length)];
23 |
24 | return randomColor;
25 | };
26 |
27 | // eslint-disable-next-line import/no-anonymous-default-export
28 | export default async function (req, res) {
29 | const username = req.url.split("report?username=")[1].split(".jpg")[0];
30 | const randomColor = getRandomColor();
31 |
32 | const { pr, commits, issues, stars, avatar, name } = await fetch(
33 | process.env.REPORT_API_URL + "/api/stats?username=" + username
34 | )
35 | .then((res) => res.json())
36 | .then((data) => data["data"]);
37 |
38 | return new ImageResponse(
39 | (
40 |
50 |
60 |
68 |
76 |
77 |
88 | {name.toString()}
89 |
90 | @{username.toString()}
91 |
92 |
93 |
94 |
95 |
156 |
157 |
158 |
170 |
179 |
192 |
196 | {statsFormatter(commits["total_count"].toString())}
197 |
198 |
202 | commits
203 |
204 |
205 |
206 |
220 |
224 | {statsFormatter(stars)}
225 |
226 |
230 | stars
231 |
232 |
233 |
234 |
235 |
245 |
258 |
262 | {statsFormatter(issues.toString())}
263 |
264 |
268 | issues
269 |
270 |
271 |
272 |
286 |
290 | {statsFormatter(pr.toString())}
291 |
292 |
296 | pull requests
297 |
298 |
299 |
300 |
301 |
302 | ),
303 | {
304 | width: 950,
305 | height: 496,
306 | }
307 | );
308 | }
309 |
--------------------------------------------------------------------------------
/pages/api/stats.js:
--------------------------------------------------------------------------------
1 | const axios = require("axios");
2 | const JSSoup = require("jssoup").default;
3 |
4 | require("dotenv").config();
5 |
6 | const { multiTokenizer } = require("./multitokenizer");
7 |
8 | // Github Search REST API currently does not support sorting repositories by number of stars
9 | const fetchTotalStarsAndPRs = (parameters, token) => {
10 | return axios({
11 | url: "https://api.github.com/graphql",
12 | method: "post",
13 | headers: {
14 | Authorization: `bearer ${token}`,
15 | },
16 | data: {
17 | query: `
18 | query userInfo($login: String!) {
19 | user(login: $login) {
20 | pullRequests(after: "2022-01-01", before: "2022-12-31") {
21 | totalCount
22 | }
23 | avatarUrl
24 | name
25 | repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}) {
26 | totalCount
27 | nodes {
28 | stargazers {
29 | totalCount
30 | }
31 | }
32 | }
33 | }
34 | }
35 | `,
36 | variables: parameters,
37 | },
38 | }).then((e) => {
39 | return {
40 | pr: e.data.data.user.pullRequests.totalCount,
41 | name: e.data.data.user.name,
42 | avatar: e.data.data.user.avatarUrl,
43 | stars: e.data.data.user.repositories.nodes.reduce((prev, curr) => {
44 | return prev + curr.stargazers.totalCount;
45 | }, 0),
46 | };
47 | });
48 | };
49 |
50 | // https://docs.github.com/en/free-pro-team@latest/rest/reference/search#search-commits
51 | const fetchTotalCommits = (params, token) => {
52 | return axios({
53 | method: "get",
54 | url: `https://github.com/users/${params.login}/contributions?from=2022-01-01`,
55 | headers: {
56 | "Content-Type": "application/json",
57 | Accept: "application/vnd.github.cloak-preview",
58 | Authorization: `bearer ${token}`,
59 | },
60 | }).then((e) => {
61 | const soup = new JSSoup(e.data);
62 | const selector = soup.findAll("rect");
63 | let commitsCount = 0;
64 |
65 | const commits = selector.map((commit) => {
66 | const count = commit.attrs["data-count"];
67 | const day = commit.attrs["data-date"];
68 | const level = commit.attrs["data-level"];
69 |
70 | if (parseInt(count)) commitsCount += parseInt(count);
71 |
72 | return {
73 | count,
74 | day,
75 | level,
76 | };
77 | });
78 |
79 | return { total_count: commitsCount, data: commits };
80 | });
81 | };
82 |
83 | //https://docs.github.com/en/free-pro-team@latest/rest/reference/search#search-issues-and-pull-requests
84 | const fetchTotalIssues = (params, token) => {
85 | return axios({
86 | method: "get",
87 | url: `https://api.github.com/search/issues?q=author:${params.login}+is:issue+created:2022-01-01..2022-12-31`,
88 | headers: {
89 | "Content-Type": "application/json",
90 | Accept: "application/vnd.github.cloak-preview",
91 | Authorization: `bearer ${token}`,
92 | },
93 | }).then((e) => e.data["total_count"]);
94 | };
95 |
96 | export const statsFetch = async (parameters) => {
97 | const _stats = {
98 | name: "",
99 | avatar: "",
100 | pr: 0,
101 | stars: 0,
102 | issues: 0,
103 | commits: 0,
104 | };
105 |
106 | // If user hasn't authenticated with github, use the public API
107 | if (!parameters.token) {
108 | const { pr, stars, name, avatar } = await fetcher(
109 | fetchTotalStarsAndPRs,
110 | parameters
111 | );
112 |
113 | _stats.commits = await fetcher(fetchTotalCommits, parameters);
114 | _stats.issues = await fetcher(fetchTotalIssues, parameters);
115 | _stats.pr = pr;
116 | _stats.stars = stars;
117 | _stats.avatar = avatar;
118 | _stats.name = name;
119 | }
120 | // If user has authenticated with github, use their access toen
121 | else {
122 | const { pr, stars, name, avatar } = await fetchTotalStarsAndPRs(
123 | { login: parameters["login"] },
124 | parameters["token"]
125 | );
126 | _stats.commits = await fetchTotalCommits(parameters, parameters["token"]);
127 | _stats.issues = await fetchTotalIssues(parameters, parameters["token"]);
128 | _stats.pr = pr;
129 | _stats.stars = stars;
130 | _stats.avatar = avatar;
131 | _stats.name = name;
132 | }
133 |
134 | return _stats;
135 | };
136 |
137 | const fetcher = async (func, params) => {
138 | try {
139 | let res = await multiTokenizer(func, params, 0);
140 | return res;
141 | } catch (e) {
142 | console.log(e);
143 | return 0;
144 | }
145 | };
146 |
147 | module.exports = async (req, res) => {
148 | try {
149 | const { username, token } = req.query;
150 | const stats = await statsFetch({ login: username, token: token });
151 |
152 | // Headers
153 | res.setHeader("Content-Type", "application/json");
154 | res.setHeader("Cache-Control", `public, max-age=1800`);
155 |
156 | return res.send({
157 | data: { ...stats },
158 | generated_at: new Date().getTime(),
159 | error_code: 0,
160 | });
161 | } catch (err) {
162 | return res.send({
163 | error_code: "001",
164 | message: err.message,
165 | secondary: err.secondaryMessage,
166 | });
167 | }
168 | };
169 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/no-img-element */
2 | import Head from "next/head";
3 | import Image from "next/image";
4 |
5 | import { useRouter } from "next/router";
6 | import { useState, useRef } from "react";
7 |
8 | // Third-Party
9 | import Particles from "react-tsparticles";
10 | import GitHubOAuthButton from "../components/GithubOAuthButton/GithubOAuthButton";
11 |
12 | // Constants
13 | import { particleOptions } from "../constants/particleOptions";
14 |
15 | export default function Home() {
16 | // State
17 | const [username, setUsername] = useState("");
18 |
19 | // References
20 | const wrapItUpRef = useRef(null);
21 | const landingFlowBRef = useRef(null);
22 |
23 | // Hooks
24 | const router = useRouter();
25 |
26 | const handleUserNameChange = (event) => {
27 | const labelClassList = wrapItUpRef.current.classList;
28 | const flowBClassList = landingFlowBRef.current.classList;
29 | const containsText = event.target.value.trim().length > 0;
30 |
31 | if (containsText && labelClassList[0] === "hidden") {
32 | labelClassList.remove("hidden");
33 | flowBClassList.add("hidden");
34 | } else if (!containsText && !labelClassList[0] !== "hidden") {
35 | labelClassList.add("hidden");
36 | flowBClassList.remove("hidden");
37 | }
38 | setUsername(event.target.value);
39 | };
40 |
41 | const handleKeyPress = (event) => {
42 | // Optional attribute for IE
43 | if (event?.code === "Enter") handleUsernameSubmit();
44 | };
45 |
46 | const handleUsernameSubmit = (e) => router.push(`/${username}`);
47 |
48 | return (
49 |
50 |
51 |
GitHub Wrapped
52 |
53 |
54 |
55 |
56 |
57 |
64 |
65 |
66 |
#GitHubWrapped
67 |
We're ready for 2022!
68 |
69 | Let's take a look at all the contributions you made to the
70 | open-source community in the last 52 weeks!
71 |
72 |
73 |
74 |
83 |
84 |
89 | Wrap It Up !
90 |
91 |
92 |
93 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | *This project is neither maintained nor endorsed by GitHub.
105 |
106 |
107 |
108 | );
109 | }
110 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ishandeveloper/github-wrapped/b652547a3ac23e1cff96fc6ddc03e6a799d2d7d7/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ishandeveloper/github-wrapped/b652547a3ac23e1cff96fc6ddc03e6a799d2d7d7/public/favicon.png
--------------------------------------------------------------------------------
/public/icons/check.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/icons/download.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/icons/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/icons/linkedin.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/icons/twitter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/trophy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ishandeveloper/github-wrapped/b652547a3ac23e1cff96fc6ddc03e6a799d2d7d7/public/images/trophy.png
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/styles/Report.module.scss:
--------------------------------------------------------------------------------
1 | .logo {
2 | position: fixed;
3 | top: 1.5rem;
4 | left: 1.5rem;
5 | display: flex;
6 | cursor: pointer;
7 | align-items: center;
8 |
9 | img {
10 | height: 54px;
11 | width: 54px;
12 | }
13 |
14 | h3 {
15 | margin-left: 1rem;
16 | font-weight: 600;
17 | font-size: 1.3rem;
18 | }
19 | }
20 |
21 | .disclaimer {
22 | position: fixed;
23 | bottom: 1rem;
24 | left: 1rem;
25 | font-size: 0.75rem;
26 | color: var(--color-text-secondary);
27 | }
28 |
29 | .wrapper {
30 | margin-top: 5rem;
31 | width: 100%;
32 | padding: 2.5rem 5rem;
33 | gap: 2rem;
34 | display: flex;
35 | flex-direction: column;
36 | }
37 |
38 | .main {
39 | height: 400px;
40 | overflow: hidden;
41 | width: 100%;
42 | display: flex;
43 | gap: 2rem;
44 | }
45 |
46 | .graph {
47 | flex: 2;
48 | cursor: move;
49 | border-radius: 8px;
50 |
51 | &.loading {
52 | background: hsl(0, 0%, 98%);
53 | background: linear-gradient(
54 | 90deg,
55 | hsl(0, 0%, 98%) 0%,
56 | hsl(0, 0%, 96%) 46%,
57 | hsl(0, 0%, 98%) 100%
58 | );
59 | background-size: cover;
60 | animation: skeleton 2s infinite ease-in-out;
61 | animation-direction: alternate-reverse;
62 | }
63 |
64 | @keyframes skeleton {
65 | 0% {
66 | background-position: -800px 0;
67 | }
68 | 100% {
69 | background-position: 800px 0;
70 | }
71 | }
72 | }
73 | .actions {
74 | flex: 0.8;
75 | display: flex;
76 | flex-direction: column;
77 | gap: 1rem;
78 | }
79 | .action {
80 | background: #f3f7f7;
81 | width: 100%;
82 | border-radius: 8px;
83 | padding: 1.75rem 1.2rem;
84 | height: max-content;
85 |
86 | h3 {
87 | font-weight: 500;
88 | margin-bottom: 0.75rem;
89 | }
90 |
91 | p {
92 | line-height: 1.3;
93 | font-weight: 300;
94 | margin-bottom: 1.2rem;
95 | }
96 | }
97 |
98 | .info {
99 | display: flex;
100 | gap: 2rem;
101 | }
102 |
103 | .infoCard {
104 | display: flex;
105 | flex: 2;
106 | justify-content: flex-start;
107 | align-items: flex-start;
108 | position: relative;
109 |
110 | h3 {
111 | top: 50%;
112 | left: 1rem;
113 | position: absolute;
114 | writing-mode: vertical-lr;
115 | transform: translateY(-50%) rotate(180deg);
116 | font-size: 1rem;
117 | font-weight: 300;
118 | text-transform: uppercase;
119 | color: #8e8e8e;
120 | height: 300px;
121 | text-align: center;
122 | vertical-align: middle;
123 | letter-spacing: 0.16rem;
124 | span {
125 | color: #113692;
126 | }
127 | }
128 | svg {
129 | margin-left: 4rem;
130 | max-width: 100%;
131 | }
132 | }
133 |
134 | .social {
135 | flex: 1;
136 | display: flex;
137 | flex-direction: column;
138 | justify-content: center;
139 | border-radius: 8px;
140 |
141 | h3 {
142 | font-size: 1.6rem;
143 | font-weight: 500;
144 | margin-bottom: 0.5rem;
145 | }
146 |
147 | p {
148 | line-height: 1.4;
149 | font-weight: 300;
150 | margin-bottom: 1.5rem;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/styles/globals.scss:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap");
2 |
3 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
4 |
5 | /* Document
6 | ========================================================================== */
7 |
8 | /**
9 | * 1. Correct the line height in all browsers.
10 | * 2. Prevent adjustments of font size after orientation changes in iOS.
11 | */
12 |
13 | html {
14 | line-height: 1.15; /* 1 */
15 | -webkit-text-size-adjust: 100%; /* 2 */
16 | }
17 |
18 | /* Sections
19 | ========================================================================== */
20 |
21 | /**
22 | * Remove the margin in all browsers.
23 | */
24 |
25 | body {
26 | margin: 0;
27 | }
28 |
29 | /**
30 | * Render the `main` element consistently in IE.
31 | */
32 |
33 | main {
34 | display: block;
35 | }
36 |
37 | /**
38 | * Correct the font size and margin on `h1` elements within `section` and
39 | * `article` contexts in Chrome, Firefox, and Safari.
40 | */
41 |
42 | h1 {
43 | font-size: 2em;
44 | margin: 0.67em 0;
45 | }
46 |
47 | /* Grouping content
48 | ========================================================================== */
49 |
50 | /**
51 | * 1. Add the correct box sizing in Firefox.
52 | * 2. Show the overflow in Edge and IE.
53 | */
54 |
55 | hr {
56 | box-sizing: content-box; /* 1 */
57 | height: 0; /* 1 */
58 | overflow: visible; /* 2 */
59 | }
60 |
61 | /**
62 | * 1. Correct the inheritance and scaling of font size in all browsers.
63 | * 2. Correct the odd `em` font sizing in all browsers.
64 | */
65 |
66 | pre {
67 | font-family: monospace, monospace; /* 1 */
68 | font-size: 1em; /* 2 */
69 | }
70 |
71 | /* Text-level semantics
72 | ========================================================================== */
73 |
74 | /**
75 | * Remove the gray background on active links in IE 10.
76 | */
77 |
78 | a {
79 | background-color: transparent;
80 | }
81 |
82 | /**
83 | * 1. Remove the bottom border in Chrome 57-
84 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
85 | */
86 |
87 | abbr[title] {
88 | border-bottom: none; /* 1 */
89 | text-decoration: underline; /* 2 */
90 | text-decoration: underline dotted; /* 2 */
91 | }
92 |
93 | /**
94 | * Add the correct font weight in Chrome, Edge, and Safari.
95 | */
96 |
97 | b,
98 | strong {
99 | font-weight: bolder;
100 | }
101 |
102 | /**
103 | * 1. Correct the inheritance and scaling of font size in all browsers.
104 | * 2. Correct the odd `em` font sizing in all browsers.
105 | */
106 |
107 | code,
108 | kbd,
109 | samp {
110 | font-family: monospace, monospace; /* 1 */
111 | font-size: 1em; /* 2 */
112 | }
113 |
114 | /**
115 | * Add the correct font size in all browsers.
116 | */
117 |
118 | small {
119 | font-size: 80%;
120 | }
121 |
122 | /**
123 | * Prevent `sub` and `sup` elements from affecting the line height in
124 | * all browsers.
125 | */
126 |
127 | sub,
128 | sup {
129 | font-size: 75%;
130 | line-height: 0;
131 | position: relative;
132 | vertical-align: baseline;
133 | }
134 |
135 | sub {
136 | bottom: -0.25em;
137 | }
138 |
139 | sup {
140 | top: -0.5em;
141 | }
142 |
143 | /* Embedded content
144 | ========================================================================== */
145 |
146 | /**
147 | * Remove the border on images inside links in IE 10.
148 | */
149 |
150 | img {
151 | border-style: none;
152 | }
153 |
154 | /* Forms
155 | ========================================================================== */
156 |
157 | /**
158 | * 1. Change the font styles in all browsers.
159 | * 2. Remove the margin in Firefox and Safari.
160 | */
161 |
162 | button,
163 | input,
164 | optgroup,
165 | select,
166 | textarea {
167 | font-family: inherit; /* 1 */
168 | font-size: 100%; /* 1 */
169 | line-height: 1.15; /* 1 */
170 | margin: 0; /* 2 */
171 | }
172 |
173 | /**
174 | * Show the overflow in IE.
175 | * 1. Show the overflow in Edge.
176 | */
177 |
178 | button,
179 | input {
180 | /* 1 */
181 | overflow: visible;
182 | }
183 |
184 | /**
185 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
186 | * 1. Remove the inheritance of text transform in Firefox.
187 | */
188 |
189 | button,
190 | select {
191 | /* 1 */
192 | text-transform: none;
193 | }
194 |
195 | /**
196 | * Correct the inability to style clickable types in iOS and Safari.
197 | */
198 |
199 | button,
200 | [type="button"],
201 | [type="reset"],
202 | [type="submit"] {
203 | -webkit-appearance: button;
204 | }
205 |
206 | /**
207 | * Remove the inner border and padding in Firefox.
208 | */
209 |
210 | button::-moz-focus-inner,
211 | [type="button"]::-moz-focus-inner,
212 | [type="reset"]::-moz-focus-inner,
213 | [type="submit"]::-moz-focus-inner {
214 | border-style: none;
215 | padding: 0;
216 | }
217 |
218 | /**
219 | * Restore the focus styles unset by the previous rule.
220 | */
221 |
222 | button:-moz-focusring,
223 | [type="button"]:-moz-focusring,
224 | [type="reset"]:-moz-focusring,
225 | [type="submit"]:-moz-focusring {
226 | outline: 1px dotted ButtonText;
227 | }
228 |
229 | /**
230 | * Correct the padding in Firefox.
231 | */
232 |
233 | fieldset {
234 | padding: 0.35em 0.75em 0.625em;
235 | }
236 |
237 | /**
238 | * 1. Correct the text wrapping in Edge and IE.
239 | * 2. Correct the color inheritance from `fieldset` elements in IE.
240 | * 3. Remove the padding so developers are not caught out when they zero out
241 | * `fieldset` elements in all browsers.
242 | */
243 |
244 | legend {
245 | box-sizing: border-box; /* 1 */
246 | color: inherit; /* 2 */
247 | display: table; /* 1 */
248 | max-width: 100%; /* 1 */
249 | padding: 0; /* 3 */
250 | white-space: normal; /* 1 */
251 | }
252 |
253 | /**
254 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
255 | */
256 |
257 | progress {
258 | vertical-align: baseline;
259 | }
260 |
261 | /**
262 | * Remove the default vertical scrollbar in IE 10+.
263 | */
264 |
265 | textarea {
266 | overflow: auto;
267 | }
268 |
269 | /**
270 | * 1. Add the correct box sizing in IE 10.
271 | * 2. Remove the padding in IE 10.
272 | */
273 |
274 | [type="checkbox"],
275 | [type="radio"] {
276 | box-sizing: border-box; /* 1 */
277 | padding: 0; /* 2 */
278 | }
279 |
280 | /**
281 | * Correct the cursor style of increment and decrement buttons in Chrome.
282 | */
283 |
284 | [type="number"]::-webkit-inner-spin-button,
285 | [type="number"]::-webkit-outer-spin-button {
286 | height: auto;
287 | }
288 |
289 | /**
290 | * 1. Correct the odd appearance in Chrome and Safari.
291 | * 2. Correct the outline style in Safari.
292 | */
293 |
294 | [type="search"] {
295 | -webkit-appearance: textfield; /* 1 */
296 | outline-offset: -2px; /* 2 */
297 | }
298 |
299 | /**
300 | * Remove the inner padding in Chrome and Safari on macOS.
301 | */
302 |
303 | [type="search"]::-webkit-search-decoration {
304 | -webkit-appearance: none;
305 | }
306 |
307 | /**
308 | * 1. Correct the inability to style clickable types in iOS and Safari.
309 | * 2. Change font properties to `inherit` in Safari.
310 | */
311 |
312 | ::-webkit-file-upload-button {
313 | -webkit-appearance: button; /* 1 */
314 | font: inherit; /* 2 */
315 | }
316 |
317 | /* Interactive
318 | ========================================================================== */
319 |
320 | /*
321 | * Add the correct display in Edge, IE 10+, and Firefox.
322 | */
323 |
324 | details {
325 | display: block;
326 | }
327 |
328 | /*
329 | * Add the correct display in all browsers.
330 | */
331 |
332 | summary {
333 | display: list-item;
334 | }
335 |
336 | /* Misc
337 | ========================================================================== */
338 |
339 | /**
340 | * Add the correct display in IE 10+.
341 | */
342 |
343 | template {
344 | display: none;
345 | }
346 |
347 | /**
348 | * Add the correct display in IE 10.
349 | */
350 |
351 | [hidden] {
352 | display: none;
353 | }
354 |
355 | * {
356 | margin: 0;
357 | padding: 0;
358 | box-sizing: border-box;
359 | font-family: "Poppins", sans-serif;
360 | }
361 |
362 | :root {
363 | --color-text: #000000;
364 | --color-primary: #113692;
365 | --color-input-bg: #e8e8e8;
366 | --color-text-secondary: #7d7d7d;
367 | --square-size: 15px;
368 | --square-gap: 5px;
369 | font-size: 16px;
370 | }
371 |
372 | .graph-loading {
373 | display: flex;
374 | align-items: center;
375 | justify-content: center;
376 |
377 | .squares {
378 | transform: translate(-10%, -25%);
379 | list-style: none;
380 | display: grid;
381 | grid-gap: var(--square-gap);
382 | grid-template-rows: repeat(7, var(--square-size));
383 | grid-auto-flow: column;
384 | grid-auto-columns: var(--square-size);
385 | }
386 |
387 | .squares li {
388 | background-color: hsl(216, 14%, 95%);
389 | }
390 |
391 | .squares li[data-level="0"] {
392 | animation: level0 2s infinite;
393 | }
394 |
395 | @keyframes level0 {
396 | 0% {
397 | background-color: hsl(216, 14%, 95%);
398 | }
399 |
400 | 20% {
401 | background-color: hsl(223, 71%, 85%);
402 | }
403 |
404 | 40% {
405 | background-color: hsl(223, 69%, 65%);
406 | }
407 | 60% {
408 | background-color: hsl(223, 80%, 62%);
409 | }
410 | 80% {
411 | background-color: hsl(223, 71%, 85%);
412 | }
413 | 100% {
414 | background-color: hsl(216, 14%, 95%);
415 | }
416 | }
417 |
418 | .squares li[data-level="1"] {
419 | background-color: hsl(223, 100%, 70%);
420 | animation: level1 2s infinite;
421 | }
422 |
423 | @keyframes level1 {
424 | 0% {
425 | background-color: hsl(223, 100%, 70%);
426 | }
427 |
428 | 20% {
429 | background-color: hsl(223, 100%, 60%);
430 | }
431 |
432 | 40% {
433 | background-color: hsl(223, 100%, 50%);
434 | }
435 | 60% {
436 | background-color: hsl(223, 100%, 600%);
437 | }
438 | 80% {
439 | background-color: hsl(223, 100%, 70%);
440 | }
441 | 100% {
442 | background-color: hsl(223, 100%, 70%);
443 | }
444 | }
445 | }
446 |
447 | .github-button-wrapper{
448 | position: fixed;
449 | bottom: 1rem;
450 | right: 1rem;
451 | }
--------------------------------------------------------------------------------
/styles/landing.scss:
--------------------------------------------------------------------------------
1 | .landing {
2 | position: relative;
3 | min-height: 100vh;
4 | overflow-y: hidden;
5 |
6 | &__logo {
7 | position: fixed;
8 | top: 1.5rem;
9 | left: 1.5rem;
10 | cursor: pointer;
11 |
12 | height: 54px;
13 | width: 54px;
14 | }
15 |
16 | &__content {
17 | z-index: 2;
18 | display: flex;
19 | flex-direction: column;
20 | align-items: center;
21 | justify-content: center;
22 | position: absolute;
23 | width: 100%;
24 | padding: 0 1rem;
25 | top: 30%;
26 | left: 0;
27 | transform: translate(0%, -50%);
28 |
29 | @media only screen and (max-width: 720px) {
30 | top: 45%;
31 | }
32 | }
33 |
34 | &__title {
35 | font-size: 2.5rem;
36 | font-weight: 500;
37 | color: var(--color-text);
38 | text-align: center;
39 |
40 | @media only screen and (max-width: 720px) {
41 | font-size: 1.4rem;
42 | }
43 | }
44 |
45 | &__message {
46 | font-size: 3.5rem;
47 | font-weight: 600;
48 | letter-spacing: 1.2px;
49 | color: var(--color-primary);
50 | text-align: center;
51 |
52 | @media only screen and (max-width: 720px) {
53 | font-size: 2.5rem;
54 | }
55 | }
56 |
57 | &__text {
58 | max-width: 600px;
59 | line-height: 1.75;
60 | text-align: center;
61 | font-size: 1.1rem;
62 | font-weight: 300;
63 | margin: 1rem 0;
64 | text-align: center;
65 | @media only screen and (max-width: 720px) {
66 | font-size: 1rem;
67 | }
68 | }
69 |
70 | &__flow-a {
71 | position: relative;
72 | display: flex;
73 | flex-direction: column;
74 | width: 100%;
75 | align-items: center;
76 | justify-content: center;
77 |
78 | button {
79 | margin-top: 0.5rem;
80 | color: #fff;
81 | background: var(--color-primary);
82 | padding: 1rem 4rem;
83 | min-width: 15em;
84 | width: 18em;
85 | border: none;
86 | border-radius: 2px;
87 | box-shadow: 2px 4px 8px 0px rgba(0, 0, 0, 0.15);
88 | font-weight: 500;
89 | font-size: 1.1rem;
90 | text-align: center;
91 | transition: all ease-in-out 250ms;
92 | position: absolute;
93 | z-index: 2;
94 | cursor: pointer;
95 | top: 60px;
96 | opacity: 1;
97 |
98 | &.hidden {
99 | top: 0px;
100 | opacity: 0;
101 | }
102 | }
103 | }
104 |
105 | &__input {
106 | margin-top: 0.5rem;
107 | background: var(--color-input-bg);
108 | color: var(--color-text);
109 | z-index: 3;
110 | padding: 0.75rem;
111 | min-width: 15em;
112 | width: 18em;
113 | font-size: 1.1rem;
114 | border: none;
115 | border-radius: 2px;
116 | outline: none;
117 | box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.1), 0 0 0 transparent;
118 | text-align: center;
119 | transition: all ease-in-out 250ms;
120 |
121 | &:focus {
122 | box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.2), 0 0 0 transparent;
123 | }
124 |
125 | &::placeholder {
126 | color: var(--color-text-secondary);
127 | }
128 | }
129 |
130 | &__separator {
131 | position: relative;
132 | color: var(--color-text-secondary);
133 | margin: 0.75rem 0;
134 | font-weight: 300;
135 | text-align: center;
136 |
137 | &::before {
138 | content: "";
139 | position: absolute;
140 | left: 0%;
141 | top: 50%;
142 | transform: translate(-55%, -50%);
143 | min-width: 6rem;
144 | width: 12rem;
145 | height: 1px;
146 | opacity: 0.5;
147 | background: var(--color-text-secondary);
148 |
149 | @media only screen and (max-width: 720px) {
150 | width: 10rem;
151 | }
152 | }
153 |
154 | &::after {
155 | content: "";
156 | position: absolute;
157 | top: 50%;
158 | transform: translate(5%, -50%);
159 | min-width: 6rem;
160 | width: 12rem;
161 | height: 1px;
162 | opacity: 0.5;
163 | background: var(--color-text-secondary);
164 |
165 | @media only screen and (max-width: 720px) {
166 | width: 10rem;
167 | }
168 | }
169 | }
170 |
171 | &__flow-b {
172 | transition: all ease-in-out 250ms;
173 | transform: translateY(0%) scale(1);
174 | opacity: 1;
175 |
176 | &.hidden {
177 | transform: translateY(150%), scale(0);
178 | opacity: 0;
179 | }
180 | }
181 |
182 | &__trophy {
183 | position: fixed;
184 | top: calc(10% + 328px);
185 | width: 100%;
186 | display: flex;
187 | align-items: center;
188 | z-index: 0;
189 | justify-content: center;
190 |
191 | img {
192 | width: 40%;
193 | transform: scale(1);
194 | transform-origin: bottom;
195 | transition: all ease-in-out 250ms;
196 | cursor: pointer;
197 |
198 | &:hover {
199 | transform: scale(1.15);
200 | }
201 | }
202 |
203 | @media only screen and (max-width: 720px) {
204 | display: none;
205 | }
206 | }
207 |
208 | &__disclaimer {
209 | position: fixed;
210 | bottom: 1rem;
211 | left: 1rem;
212 | font-size: 0.75rem;
213 | color: var(--color-text-secondary);
214 | }
215 | }
216 |
--------------------------------------------------------------------------------