Files
nutriscan/docs/resedesign/extracted-template.html

2549 lines
108 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="pt-BR"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>NutriScan · Redesign</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<style>/* latin-ext */
@font-face {
font-family: 'Instrument Serif';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url("60fa588b-1760-4d48-b4cc-04aa12eb709f") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Instrument Serif';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url("aaf27971-679d-4cf1-8dc2-75768121d40c") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
font-family: 'Instrument Serif';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("129d834f-f5f8-4f19-af81-0920e9288457") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Instrument Serif';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("72b67702-a356-405e-8772-6f0a2a9b136f") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("66e75bcd-2938-4e43-b87c-4720c7ceb8df") format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("64671880-b4e5-4ebc-8150-30f9a3eae5fa") format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("cb114583-31ec-4a43-8c57-9946423fdd16") format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("125ff4dd-646d-4f13-816d-177869568bce") format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("28bdc14b-6ceb-424a-be18-8bebc3e41446") format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("2fa653f7-680d-43f8-9aea-adfaac230e8f") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("17662fa0-bb4d-46cc-b4f5-e2727b1192a7") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("66e75bcd-2938-4e43-b87c-4720c7ceb8df") format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("64671880-b4e5-4ebc-8150-30f9a3eae5fa") format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("cb114583-31ec-4a43-8c57-9946423fdd16") format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("125ff4dd-646d-4f13-816d-177869568bce") format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("28bdc14b-6ceb-424a-be18-8bebc3e41446") format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("2fa653f7-680d-43f8-9aea-adfaac230e8f") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("17662fa0-bb4d-46cc-b4f5-e2727b1192a7") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("66e75bcd-2938-4e43-b87c-4720c7ceb8df") format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("64671880-b4e5-4ebc-8150-30f9a3eae5fa") format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("cb114583-31ec-4a43-8c57-9946423fdd16") format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("125ff4dd-646d-4f13-816d-177869568bce") format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("28bdc14b-6ceb-424a-be18-8bebc3e41446") format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("2fa653f7-680d-43f8-9aea-adfaac230e8f") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("17662fa0-bb4d-46cc-b4f5-e2727b1192a7") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("66e75bcd-2938-4e43-b87c-4720c7ceb8df") format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("64671880-b4e5-4ebc-8150-30f9a3eae5fa") format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("cb114583-31ec-4a43-8c57-9946423fdd16") format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("125ff4dd-646d-4f13-816d-177869568bce") format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("28bdc14b-6ceb-424a-be18-8bebc3e41446") format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("2fa653f7-680d-43f8-9aea-adfaac230e8f") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("17662fa0-bb4d-46cc-b4f5-e2727b1192a7") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url("66e75bcd-2938-4e43-b87c-4720c7ceb8df") format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url("64671880-b4e5-4ebc-8150-30f9a3eae5fa") format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url("cb114583-31ec-4a43-8c57-9946423fdd16") format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url("125ff4dd-646d-4f13-816d-177869568bce") format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url("28bdc14b-6ceb-424a-be18-8bebc3e41446") format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url("2fa653f7-680d-43f8-9aea-adfaac230e8f") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url("17662fa0-bb4d-46cc-b4f5-e2727b1192a7") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("f9f4dc7f-2701-42e0-86dc-7bcf245a2604") format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("29794d15-84b2-434f-9f1a-55cc15fe1b52") format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("ffb59275-de9f-4fde-aef2-62a6cdd8975c") format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("95819523-59ca-457b-9908-17bb06391b07") format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("430de5df-e28e-4105-bd9c-76245f0b6278") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("aee23ff6-7c42-40e3-8aea-b66717c9b112") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("f9f4dc7f-2701-42e0-86dc-7bcf245a2604") format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("29794d15-84b2-434f-9f1a-55cc15fe1b52") format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("ffb59275-de9f-4fde-aef2-62a6cdd8975c") format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("95819523-59ca-457b-9908-17bb06391b07") format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("430de5df-e28e-4105-bd9c-76245f0b6278") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("aee23ff6-7c42-40e3-8aea-b66717c9b112") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("f9f4dc7f-2701-42e0-86dc-7bcf245a2604") format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("29794d15-84b2-434f-9f1a-55cc15fe1b52") format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("ffb59275-de9f-4fde-aef2-62a6cdd8975c") format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("95819523-59ca-457b-9908-17bb06391b07") format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("430de5df-e28e-4105-bd9c-76245f0b6278") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("aee23ff6-7c42-40e3-8aea-b66717c9b112") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* devanagari */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("7d3f970d-0898-4a35-8b8b-81a4d9589287") format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
}
/* latin-ext */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("c87d3501-6491-4b94-a25f-db6af8b3a4e2") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("39d0704e-6c5f-4384-9782-44a1cac440fb") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* devanagari */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("3599d94e-9ff5-4e03-ada0-1499f576e5f9") format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
}
/* latin-ext */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("cef94c28-202f-47fe-b176-88776060c772") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("e1a9a3d5-8c5c-4d49-89a4-64e72d166ed6") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* devanagari */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("f7669cdb-bd09-4abe-8ded-d8900a26abff") format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
}
/* latin-ext */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("b76d5710-f204-493b-9f5a-fae88b0e473d") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("fcc0ac01-6cbe-4b59-a833-887efa041aad") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* devanagari */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("9f79dfe8-e074-40ca-9c4e-6075fdd7e839") format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
}
/* latin-ext */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("ad19fdb0-2a18-49a1-b04d-d8b4c39ca9a2") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("3af5f93f-660a-478d-abd2-98c71b5b0352") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* devanagari */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url("b80336d1-7601-4186-9cdb-cf36c1b094e3") format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
}
/* latin-ext */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url("012fc321-5b5a-4bce-b667-406f8852137c") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url("3db8eda8-1f87-43b5-91fa-6a0cbee3fa7a") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* devanagari */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url("2b8b3dd1-7692-40c5-845b-3db6b6105a31") format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
}
/* latin-ext */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url("3ceac7e6-54ab-4a00-b1e1-4b085759f1a1") format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url("4a4421f6-2d21-4902-bee8-7e5e6974a8c3") format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
</style>
<style>
/* ============================================================
NutriScan — Revamped Design System
Editorial, trustworthy, health-focused
============================================================ */
/* Tokens */
:root {
/* Warm neutrals — stone-tinted */
--ink-900: #141312;
--ink-800: #26231f;
--ink-700: #3d3934;
--ink-600: #595149;
--ink-500: #7a7269;
--ink-400: #a39c93;
--ink-300: #c9c3ba;
--ink-200: #e4dfd7;
--ink-150: #ece7de;
--ink-100: #f3efe7;
--ink-50: #f9f6f0;
--ink-0: #fdfbf5;
/* Accent — clinical green (health, trustworthy, muted) */
--accent: oklch(52% 0.11 155);
--accent-hover: oklch(46% 0.11 155);
--accent-soft: oklch(94% 0.04 155);
--accent-ink: oklch(28% 0.06 155);
/* Nutri-Score — keep official palette (regulated meaning) */
--nutri-a: #038141;
--nutri-b: #85bb2f;
--nutri-c: #fecb02;
--nutri-d: #ee8100;
--nutri-e: #e63e11;
/* Semantic */
--warn: #b45309;
--warn-soft: #fef3c7;
--danger: #b91c1c;
/* Radii */
--r-sm: 6px;
--r-md: 10px;
--r-lg: 16px;
--r-xl: 24px;
/* Type */
--font-serif: 'Instrument Serif', 'Iowan Old Style', Georgia, serif;
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
}
.theme-dark {
--ink-900: #f3efe7;
--ink-800: #e4dfd7;
--ink-700: #c9c3ba;
--ink-600: #a39c93;
--ink-500: #7a7269;
--ink-400: #595149;
--ink-300: #3d3934;
--ink-200: #2a2622;
--ink-150: #22201d;
--ink-100: #1c1a17;
--ink-50: #161412;
--ink-0: #0f0e0c;
--accent: oklch(68% 0.12 155);
--accent-hover: oklch(74% 0.12 155);
--accent-soft: oklch(28% 0.05 155);
--accent-ink: oklch(88% 0.06 155);
}
/* Reset-ish */
* { box-sizing: border-box; }
body, h1, h2, h3, h4, h5, p, ul, ol, figure, blockquote {
margin: 0;
}
ul, ol { padding-left: 0; list-style: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
img, svg { display: block; }
a { color: inherit; text-decoration: none; }
body {
font-family: var(--font-sans);
font-feature-settings: 'ss01', 'cv11';
color: var(--ink-900);
background: var(--ink-0);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
line-height: 1.5;
}
/* ============================================================
Type scale — editorial
============================================================ */
.display {
font-family: var(--font-serif);
font-weight: 400;
letter-spacing: -0.015em;
line-height: 1.02;
font-size: clamp(44px, 7vw, 96px);
}
.display-sm {
font-family: var(--font-serif);
font-weight: 400;
letter-spacing: -0.01em;
line-height: 1.04;
font-size: clamp(32px, 4vw, 52px);
}
.eyebrow {
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--ink-500);
font-weight: 500;
}
.label-sm {
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--ink-500);
font-weight: 500;
}
/* ============================================================
Nutri-Score scale badge — official style, simplified
============================================================ */
.nutri-badge {
display: inline-flex;
align-items: center;
font-family: var(--font-sans);
font-weight: 800;
border-radius: 999px;
overflow: hidden;
background: var(--ink-100);
padding: 3px;
gap: 2px;
}
.nutri-badge .seg {
width: 26px; height: 26px;
border-radius: 999px;
display: flex; align-items: center; justify-content: center;
color: #fff;
font-size: 12px;
letter-spacing: -0.02em;
transition: transform 0.25s;
opacity: 0.35;
filter: saturate(0.3);
}
.nutri-badge .seg.active {
opacity: 1; filter: none;
transform: scale(1.15);
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.nutri-badge .seg.a { background: var(--nutri-a); }
.nutri-badge .seg.b { background: var(--nutri-b); }
.nutri-badge .seg.c { background: var(--nutri-c); color: #3a2a00; }
.nutri-badge .seg.d { background: var(--nutri-d); }
.nutri-badge .seg.e { background: var(--nutri-e); }
.nutri-badge.lg .seg { width: 40px; height: 40px; font-size: 17px; }
.nutri-badge.sm .seg { width: 18px; height: 18px; font-size: 9px; }
/* Single big letter style — for hero metric */
.nutri-letter {
width: 88px; height: 88px;
border-radius: 50%;
display: inline-flex; align-items: center; justify-content: center;
font-family: var(--font-serif);
font-size: 58px;
color: #fff;
font-weight: 400;
letter-spacing: -0.03em;
line-height: 1;
position: relative;
box-shadow: inset 0 -3px 0 rgba(0,0,0,0.15), 0 8px 24px -8px rgba(0,0,0,0.2);
}
.nutri-letter.a { background: var(--nutri-a); }
.nutri-letter.b { background: var(--nutri-b); }
.nutri-letter.c { background: var(--nutri-c); color: #3a2a00; }
.nutri-letter.d { background: var(--nutri-d); }
.nutri-letter.e { background: var(--nutri-e); }
/* ============================================================
Button system
============================================================ */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
font-family: var(--font-sans);
font-weight: 500;
font-size: 14px;
line-height: 1;
padding: 12px 18px;
border-radius: var(--r-md);
transition: all 0.2s;
cursor: pointer;
border: 1px solid transparent;
white-space: nowrap;
}
.btn-primary {
background: var(--ink-900);
color: var(--ink-0);
}
.btn-primary:hover { background: var(--ink-800); transform: translateY(-1px); }
.btn-accent {
background: var(--accent);
color: #fff;
}
.btn-accent:hover { background: var(--accent-hover); }
.btn-ghost {
background: transparent;
color: var(--ink-900);
border: 1px solid var(--ink-200);
}
.btn-ghost:hover { border-color: var(--ink-400); background: var(--ink-50); }
.btn-sm { padding: 8px 12px; font-size: 13px; }
.btn-lg { padding: 16px 24px; font-size: 15px; }
/* ============================================================
Chip / tag
============================================================ */
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 500;
background: var(--ink-100);
color: var(--ink-700);
border: 1px solid var(--ink-200);
}
.chip.warn {
background: var(--warn-soft);
color: var(--warn);
border-color: #fcd34d;
}
.chip.accent {
background: var(--accent-soft);
color: var(--accent-ink);
border-color: transparent;
}
/* ============================================================
Input
============================================================ */
.input {
width: 100%;
padding: 14px 18px;
border-radius: var(--r-md);
border: 1px solid var(--ink-200);
background: var(--ink-0);
font-family: var(--font-sans);
font-size: 15px;
color: var(--ink-900);
transition: all 0.15s;
}
.input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 4px var(--accent-soft);
}
.input::placeholder { color: var(--ink-400); }
/* ============================================================
Utilities
============================================================ */
.hairline { border-bottom: 1px solid var(--ink-200); }
.hairline-t { border-top: 1px solid var(--ink-200); }
.container { max-width: 1200px; margin: 0 auto; padding: 0 32px; }
/* Custom scrollbar — thin, warm */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--ink-200); border-radius: 10px; border: 2px solid var(--ink-0); }
::-webkit-scrollbar-thumb:hover { background: var(--ink-300); }
/* Comparison frame */
.compare-frame {
border-radius: 20px;
overflow: hidden;
background: var(--ink-0);
box-shadow: 0 1px 3px rgba(20,19,18,0.04), 0 20px 60px -20px rgba(20,19,18,0.12);
border: 1px solid var(--ink-200);
}
.compare-label {
font-family: var(--font-mono);
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.16em;
color: var(--ink-500);
padding: 10px 16px;
border-bottom: 1px solid var(--ink-200);
background: var(--ink-50);
display: flex;
align-items: center;
gap: 10px;
font-weight: 500;
}
.compare-label::before {
content: "";
width: 8px; height: 8px;
border-radius: 50%;
background: var(--ink-400);
}
.compare-label.new::before { background: var(--accent); }
/* Fade in on scroll */
.fade-in {
opacity: 0;
transform: translateY(12px);
transition: opacity 0.7s ease, transform 0.7s ease;
}
.fade-in.visible { opacity: 1; transform: none; }
/* Icon size helpers */
.i-14 { width: 14px; height: 14px; }
.i-16 { width: 16px; height: 16px; }
.i-18 { width: 18px; height: 18px; }
.i-20 { width: 20px; height: 20px; }
.i-24 { width: 24px; height: 24px; }
</style>
<style>
/* Shell chrome */
.shell {
min-height: 100vh;
background: var(--ink-50);
padding: 28px 40px 60px;
}
.shell-head {
max-width: 1400px;
margin: 0 auto 40px;
display: flex; align-items: flex-end; justify-content: space-between; gap: 24px;
flex-wrap: wrap;
}
.shell-title {
font-family: var(--font-serif);
font-size: 40px;
letter-spacing: -0.015em;
color: var(--ink-900);
line-height: 1;
}
.shell-subtitle {
font-size: 14px; color: var(--ink-500); margin-top: 10px; max-width: 560px;
}
.tabs {
display: inline-flex;
background: var(--ink-0);
border: 1px solid var(--ink-200);
border-radius: 12px;
padding: 4px;
}
.tabs button {
padding: 9px 16px;
border-radius: 8px;
font-size: 13px; font-weight: 500;
color: var(--ink-500);
display: inline-flex; align-items: center; gap: 8px;
}
.tabs button.active {
background: var(--ink-900);
color: var(--ink-0);
}
.theme-switch {
display: inline-flex; align-items: center; gap: 8px;
padding: 10px 14px; border-radius: 10px;
border: 1px solid var(--ink-200); background: var(--ink-0);
font-size: 13px; color: var(--ink-700);
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 32px;
max-width: 1400px;
margin: 0 auto;
}
.grid-2 > * { min-width: 0; }
.full {
max-width: 1400px; margin: 0 auto;
}
.frame-body {
height: 780px;
overflow: auto;
}
.frame-body.tall { height: 920px; }
.section-title {
font-family: var(--font-serif);
font-size: 28px;
letter-spacing: -0.01em;
color: var(--ink-900);
max-width: 1400px;
margin: 64px auto 20px;
display: flex; align-items: baseline; gap: 14px;
}
.section-title small {
font-family: var(--font-mono);
font-size: 11px; color: var(--ink-500);
text-transform: uppercase; letter-spacing: 0.14em; font-weight: 500;
}
.rationale {
max-width: 1400px;
margin: 0 auto 24px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
padding: 24px;
background: var(--ink-0);
border: 1px solid var(--ink-200);
border-radius: 16px;
}
.rationale h4 {
font-size: 13px; font-weight: 600; color: var(--ink-900); margin-bottom: 6px;
}
.rationale p {
font-size: 13px; color: var(--ink-600); line-height: 1.55;
}
.rationale .tag {
font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.14em;
text-transform: uppercase; color: var(--accent); margin-bottom: 10px; font-weight: 500;
display: block;
}
/* Hide scrollbars inside frames for clean capture */
.frame-body::-webkit-scrollbar { width: 6px; }
.frame-body::-webkit-scrollbar-thumb { background: var(--ink-300); }
</style>
</head>
<body>
<div id="root"></div>
<script src="e785f210-fa4d-4569-be2d-fe96636d2fb3" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="121cfed4-9dff-4d43-9e68-291a11c0c74b" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="0c059592-051e-441b-a711-b2447cf9d955" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
<script type="text/babel">
// Sample product data — mimics Open Food Facts shape for NutriScan
const SAMPLE_PRODUCTS = [
{
code: "7622210449283",
product_name: "Oreo Original",
brands: "Mondelez",
image: "oreo",
nutrition_grade: "e",
nova_group: 4,
categories: "Biscoitos, Bolachas recheadas",
allergens: ["gluten", "soy", "milk"],
nutriments: { energy: 480, fat: 20.5, sat_fat: 9.2, sugars: 38, salt: 0.7, fiber: 2.1, proteins: 5.2 },
verdict: "avoid",
},
{
code: "3017620422003",
product_name: "Nutella",
brands: "Ferrero",
image: "nutella",
nutrition_grade: "e",
nova_group: 4,
categories: "Pastas doces, Creme de avelã",
allergens: ["milk", "soy", "nuts"],
nutriments: { energy: 539, fat: 30.9, sat_fat: 10.6, sugars: 56.3, salt: 0.107, fiber: 0, proteins: 6.3 },
verdict: "avoid",
},
{
code: "7891000100103",
product_name: "Leite Integral Ninho",
brands: "Nestlé",
image: "milk",
nutrition_grade: "b",
nova_group: 1,
categories: "Laticínios, Leite em pó",
allergens: ["milk"],
nutriments: { energy: 61, fat: 3.2, sat_fat: 2.0, sugars: 4.7, salt: 0.1, fiber: 0, proteins: 3.1 },
verdict: "moderate",
},
{
code: "7891150010109",
product_name: "Aveia em Flocos Finos",
brands: "Quaker",
image: "oats",
nutrition_grade: "a",
nova_group: 1,
categories: "Cereais, Aveia",
allergens: ["gluten"],
nutriments: { energy: 371, fat: 6.6, sat_fat: 1.1, sugars: 1.1, salt: 0.005, fiber: 9.1, proteins: 13.9 },
verdict: "recommended",
},
{
code: "7894900011517",
product_name: "Coca-Cola Original 350ml",
brands: "Coca-Cola",
image: "coke",
nutrition_grade: "e",
nova_group: 4,
categories: "Bebidas, Refrigerantes",
allergens: [],
nutriments: { energy: 42, fat: 0, sat_fat: 0, sugars: 10.6, salt: 0.01, fiber: 0, proteins: 0 },
verdict: "avoid",
},
{
code: "7891024135006",
product_name: "Iogurte Natural Desnatado",
brands: "Vigor",
image: "yogurt",
nutrition_grade: "a",
nova_group: 3,
categories: "Laticínios, Iogurtes",
allergens: ["milk"],
nutriments: { energy: 41, fat: 0.1, sat_fat: 0.1, sugars: 4.5, salt: 0.12, fiber: 0, proteins: 4.6 },
verdict: "recommended",
},
{
code: "7891234567890",
product_name: "Pão de Forma Integral",
brands: "Wickbold",
image: "bread",
nutrition_grade: "c",
nova_group: 4,
categories: "Pães, Pão integral",
allergens: ["gluten", "soy"],
nutriments: { energy: 241, fat: 3.1, sat_fat: 0.6, sugars: 3.4, salt: 1.1, fiber: 6.4, proteins: 9.8 },
verdict: "moderate",
},
{
code: "7891000315507",
product_name: "Barra de Cereal Chocolate",
brands: "Nesfit",
image: "bar",
nutrition_grade: "d",
nova_group: 4,
categories: "Snacks, Barras de cereal",
allergens: ["gluten", "milk", "soy"],
nutriments: { energy: 367, fat: 7.5, sat_fat: 3.2, sugars: 22, salt: 0.32, fiber: 4.5, proteins: 5.9 },
verdict: "moderate",
},
];
// Mock product image as SVG — shows a stylized "label" with product-name type
function ProductImage({ product, size = 200 }) {
const palettes = {
oreo: { bg: "#0a1220", fg: "#f3f3f3", accent: "#2563eb" },
nutella: { bg: "#f5e9d4", fg: "#3a1d0d", accent: "#b23a1e" },
milk: { bg: "#fefbf4", fg: "#1e3a8a", accent: "#d4b88a" },
oats: { bg: "#ede3cf", fg: "#3d2f14", accent: "#9a7a3d" },
coke: { bg: "#c8161d", fg: "#ffffff", accent: "#7a0a10" },
yogurt: { bg: "#f4f9f0", fg: "#2a4d2a", accent: "#6fa65a" },
bread: { bg: "#f0dbb3", fg: "#4a2f1a", accent: "#8a5a2a" },
bar: { bg: "#3a2412", fg: "#f3e6cd", accent: "#c49a5a" },
};
const p = palettes[product.image] || palettes.oats;
const short = (product.product_name || "").split(" ").slice(0, 2).join(" ").toUpperCase();
return (
<svg viewBox="0 0 200 200" width={size} height={size} xmlns="http://www.w3.org/2000/svg" style={{ display: 'block' }}>
<rect width="200" height="200" fill={p.bg} />
{/* subtle pattern */}
<g opacity="0.08" fill={p.fg}>
<circle cx="40" cy="40" r="24" />
<circle cx="160" cy="160" r="32" />
</g>
<rect x="20" y="70" width="160" height="60" fill={p.fg} opacity="0.95" rx="2" />
<text x="100" y="105" fontFamily="Inter, sans-serif" fontSize="14" fontWeight="800" textAnchor="middle" fill={p.bg} letterSpacing="0.5">
{short}
</text>
<rect x="20" y="140" width="60" height="6" fill={p.accent} rx="1" />
<rect x="20" y="150" width="40" height="4" fill={p.fg} opacity="0.3" rx="1" />
</svg>
);
}
window.SAMPLE_PRODUCTS = SAMPLE_PRODUCTS;
window.ProductImage = ProductImage;
</script>
<script type="text/babel">
// Icons — minimal Lucide-style line icons
const Icon = {
Search: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>,
Scan: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><path d="M7 8v8M11 8v8M15 8v8"/></svg>,
Bar: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 5v14M7 5v14M11 5v14M15 5v14M19 5v14"/></svg>,
Alert: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4M12 17h.01"/></svg>,
Heart: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>,
Plus: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M5 12h14M12 5v14"/></svg>,
ArrowRight: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M5 12h14m-6-6 6 6-6 6"/></svg>,
Check: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M20 6 9 17l-5-5"/></svg>,
X: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M18 6 6 18M6 6l12 12"/></svg>,
Leaf: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19.2 2.5c1 1.5.5 7.7-2.3 11.5-2 3-5.1 4.5-6.3 4.5Z"/><path d="M2 21c0-3 1.85-5.36 5.08-6"/></svg>,
Shield: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z"/></svg>,
Globe: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10Z"/></svg>,
Book: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2Z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7Z"/></svg>,
Spark: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3Z"/></svg>,
Sun: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></svg>,
Moon: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>,
Filter: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M22 3H2l8 9.46V19l4 2v-8.54Z"/></svg>,
Grid: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>,
List: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/></svg>,
Chev: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="m9 18 6-6-6-6"/></svg>,
Back: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="m19 12H5m6-7-7 7 7 7"/></svg>,
Share: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8M16 6l-4-4-4 4M12 2v13"/></svg>,
Coffee: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M17 8h1a4 4 0 0 1 0 8h-1"/><path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z"/><path d="M6 2v3M10 2v3M14 2v3"/></svg>,
Code: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="m16 18 6-6-6-6M8 6l-6 6 6 6"/></svg>,
};
// Nutri-Score scale badge — all 5 letters with active one highlighted
function NutriScale({ active, size = "md" }) {
const cls = `nutri-badge ${size === "sm" ? "sm" : size === "lg" ? "lg" : ""}`;
return (
<div className={cls}>
{["a","b","c","d","e"].map(l => (
<div key={l} className={`seg ${l} ${active === l ? "active" : ""}`}>{l.toUpperCase()}</div>
))}
</div>
);
}
// Single big Nutri-Score letter — hero metric
function NutriLetter({ grade, size = 88 }) {
return (
<div
className={`nutri-letter ${grade}`}
style={{ width: size, height: size, fontSize: Math.round(size * 0.66) }}
>
{grade.toUpperCase()}
</div>
);
}
// NOVA group pill
function NovaPill({ group }) {
const labels = { 1: "Não processado", 2: "Ingrediente culinário", 3: "Processado", 4: "Ultraprocessado" };
const colors = { 1: "var(--nutri-a)", 2: "var(--nutri-c)", 3: "var(--nutri-d)", 4: "var(--nutri-e)" };
return (
<div style={{
display: "inline-flex", alignItems: "center", gap: 8,
padding: "4px 10px 4px 4px",
borderRadius: 999,
background: "var(--ink-100)",
border: "1px solid var(--ink-200)",
fontSize: 12,
color: "var(--ink-700)",
}}>
<span style={{
width: 22, height: 22, borderRadius: 999,
background: colors[group],
color: group === 2 ? "#3a2a00" : "#fff",
display: "grid", placeItems: "center",
fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 11,
}}>{group}</span>
NOVA · {labels[group]}
</div>
);
}
// Verdict dot
function VerdictTag({ verdict }) {
const map = {
recommended: { label: "Recomendado", color: "var(--nutri-a)" },
moderate: { label: "Com moderação", color: "var(--nutri-c)", ink: "#3a2a00" },
avoid: { label: "Evitar", color: "var(--nutri-e)" },
};
const v = map[verdict];
return (
<span style={{
display: "inline-flex", alignItems: "center", gap: 6,
fontSize: 11, fontWeight: 600,
color: v.ink || v.color,
}}>
<span style={{ width: 8, height: 8, borderRadius: 999, background: v.color }} />
{v.label}
</span>
);
}
Object.assign(window, { Icon, NutriScale, NutriLetter, NovaPill, VerdictTag });
</script>
<script type="text/babel">
// BEFORE — reproduces current NutriScan design for side-by-side comparison
// Cyberpunk-engineering style, Poppins, blue+green, heavy font weights, tech copy
function BeforeHero() {
return (
<section style={{
position: 'relative', padding: '80px 40px 100px',
background: 'var(--before-bg, #fff)', color: 'var(--before-ink, #111827)',
overflow: 'hidden', fontFamily: 'Poppins, sans-serif',
}}>
<div style={{
position: 'absolute', inset: 0,
backgroundImage: 'linear-gradient(to right, rgba(0,0,0,0.06) 1px, transparent 1px), linear-gradient(to bottom, rgba(0,0,0,0.06) 1px, transparent 1px)',
backgroundSize: '4rem 4rem',
maskImage: 'radial-gradient(ellipse 60% 50% at 50% 50%, #000 70%, transparent 100%)',
opacity: 0.5,
}} />
<div style={{
position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%,-50%)',
width: 700, height: 400, borderRadius: '50%',
background: '#0055FF', filter: 'blur(120px)', opacity: 0.06,
}} />
<div style={{ position: 'relative', zIndex: 1, maxWidth: 1000, margin: '0 auto', textAlign: 'center' }}>
<div style={{
display: 'inline-flex', alignItems: 'center', gap: 10, padding: '6px 14px',
borderRadius: 999, background: 'rgba(0,0,0,0.04)', border: '1px solid rgba(0,0,0,0.08)',
fontSize: 10, fontWeight: 900, letterSpacing: '0.2em', textTransform: 'uppercase',
color: '#6b7280', marginBottom: 28,
}}>
<span style={{ display: 'flex' }}>
<span style={{ width: 18, height: 18, borderRadius: 999, background: '#3b82f6', border: '2px solid #fff', marginRight: -6 }}/>
<span style={{ width: 18, height: 18, borderRadius: 999, background: '#10b981', border: '2px solid #fff' }}/>
</span>
NutriScan Protocol <span style={{ color: '#2563eb' }}>v2.1</span> · BR
</div>
<h1 style={{
fontSize: 'clamp(40px, 6vw, 78px)', fontWeight: 900, letterSpacing: '-0.04em',
lineHeight: 0.88, marginBottom: 24,
}}>
A verdade sobre <br/>
<span style={{
background: 'linear-gradient(90deg,#2563eb,#06b6d4,#60a5fa)',
WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
}}>seus alimentos.</span>
</h1>
<p style={{
color: '#4b5563', fontSize: 17, fontWeight: 500, maxWidth: 560, margin: '0 auto 40px', lineHeight: 1.55,
}}>
Engenharia de dados aplicada à transparência nutricional.
Descriptografamos rótulos para que você tome decisões soberanas.
</p>
<div style={{
display: 'flex', alignItems: 'center', maxWidth: 640, margin: '0 auto',
background: 'rgba(255,255,255,0.8)', backdropFilter: 'blur(10px)',
border: '1px solid rgba(0,0,0,0.08)', borderRadius: 24, padding: 8,
boxShadow: '0 8px 30px rgba(0,0,0,0.04)',
}}>
<div style={{ padding: '0 18px', color: '#9ca3af' }}>
<Icon.Scan className="i-20" />
</div>
<input placeholder="Insira o código EAN/UPC (ex: 789...)" style={{
flex: 1, border: 0, background: 'transparent', padding: '18px 8px',
fontSize: 17, fontWeight: 500, outline: 'none', color: 'inherit',
fontFamily: 'inherit',
}}/>
<button style={{
padding: '14px 24px', background: '#2563eb', color: '#fff',
border: 0, borderRadius: 14, fontSize: 11, fontWeight: 900,
letterSpacing: '0.18em', textTransform: 'uppercase', cursor: 'pointer',
fontFamily: 'inherit',
}}>Analisar Produto →</button>
</div>
<div style={{
display: 'flex', justifyContent: 'space-between', padding: '22px 14px 0', fontSize: 9,
fontFamily: 'ui-monospace, monospace', fontWeight: 700,
letterSpacing: '0.2em', textTransform: 'uppercase', color: '#3b82f6',
maxWidth: 640, margin: '0 auto',
}}>
<span>● Protocolo: Detecção de Código de Barras</span>
<span style={{ color: '#9ca3af' }}>⌘ Region: BR</span>
</div>
</div>
<div style={{
maxWidth: 900, margin: '80px auto 0', display: 'grid', gridTemplateColumns: '1fr 1fr 1fr',
gap: 48, borderTop: '1px solid rgba(0,0,0,0.08)', paddingTop: 36, opacity: 0.5,
}}>
{[{n:'2M+',l:'Dataset Entries'},{n:'100%',l:'Open Source'},{n:'< 10s',l:'Analysis Speed'}].map(x => (
<div key={x.l} style={{ textAlign: 'center' }}>
<div style={{ fontSize: 22, fontFamily: 'ui-monospace, monospace', fontWeight: 700, letterSpacing: '-0.02em' }}>{x.n}</div>
<div style={{ fontSize: 9, fontWeight: 900, letterSpacing: '0.3em', textTransform: 'uppercase', color: '#6b7280', marginTop: 4 }}>{x.l}</div>
</div>
))}
</div>
</section>
);
}
function BeforeFeatures() {
const items = [
{ bg:'#dbeafe', ink:'#2563eb', title:'Dados da Comunidade', desc:'Informações fornecidas pela Open Food Facts.', benefit: 'Base colaborativa mundial' },
{ bg:'#dcfce7', ink:'#16a34a', title:'Atualização Contínua', desc:'Novos produtos atualizados a cada minuto.', benefit: 'Tempo real' },
{ bg:'#ede9fe', ink:'#7c3aed', title:'Velocidade de Resposta', desc:'Diagnóstico nutricional em segundos.', benefit: '< 10s' },
{ bg:'#ffedd5', ink:'#ea580c', title:'Open Source', desc:'Gratuito para sempre.', benefit: 'Gratuito' },
];
return (
<section style={{ padding: '64px 40px', background: 'var(--before-bg, #fff)', fontFamily: 'Poppins, sans-serif' }}>
<div style={{ textAlign: 'center', marginBottom: 48 }}>
<h2 style={{ fontSize: 38, fontWeight: 900, letterSpacing: '-0.03em' }}>
Por que confiar no <span style={{ color: '#2563eb' }}>NutriScan</span>
</h2>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 24, maxWidth: 900, margin: '0 auto' }}>
{items.map((f,i) => (
<div key={i} style={{
background: '#f9fafb', border: '1px solid #f3f4f6',
borderRadius: 24, padding: 28,
}}>
<div style={{ width: 48, height: 48, borderRadius: 14, background: f.bg, display: 'grid', placeItems: 'center', marginBottom: 18 }}>
<Icon.Spark className="i-20" style={{ color: f.ink }}/>
</div>
<div style={{ fontSize: 20, fontWeight: 700, letterSpacing: '-0.02em', marginBottom: 8 }}>{f.title}</div>
<div style={{ color: '#6b7280', fontSize: 14, marginBottom: 16 }}>{f.desc}</div>
<div style={{
display: 'inline-flex', alignItems: 'center', gap: 6, padding: '6px 12px', borderRadius: 999,
background: f.bg, color: f.ink, fontSize: 11, fontWeight: 900, letterSpacing: '0.14em', textTransform: 'uppercase',
}}>✓ {f.benefit}</div>
</div>
))}
</div>
</section>
);
}
function BeforeProductCard({ product }) {
const gradeColors = { a: '#22c55e', b: '#4ade80', c: '#eab308', d: '#f97316', e: '#ef4444' };
const novaColors = { 1: '#22c55e', 2: '#eab308', 3: '#f97316', 4: '#ef4444' };
return (
<div style={{
background: '#fff', borderRadius: 12, border: '1px solid #f3f4f6',
overflow: 'hidden', display: 'flex', flexDirection: 'column',
boxShadow: '0 1px 2px rgba(0,0,0,0.04)',
fontFamily: 'Poppins, sans-serif',
}}>
<div style={{ position: 'relative', height: 200, background: 'linear-gradient(135deg, #f9fafb, #f3f4f6)' }}>
<div style={{ padding: 16, width: '100%', height: '100%', display: 'grid', placeItems: 'center' }}>
<ProductImage product={product} size={150}/>
</div>
<div style={{ position: 'absolute', top: 10, right: 10, background: gradeColors[product.nutrition_grade], color: '#fff', borderRadius: 6, padding: '4px 8px', fontSize: 11, fontWeight: 800, boxShadow: '0 2px 6px rgba(0,0,0,0.15)' }}>
⚡ {product.nutrition_grade.toUpperCase()}
</div>
<div style={{ position: 'absolute', top: 10, left: 10, background: novaColors[product.nova_group], color: '#fff', borderRadius: 6, padding: '4px 8px', fontSize: 11, fontWeight: 800, boxShadow: '0 2px 6px rgba(0,0,0,0.15)' }}>
NOVA {product.nova_group}
</div>
{product.allergens.length > 0 && (
<div style={{ position: 'absolute', bottom: 10, right: 10, background: '#eab308', color: '#fff', borderRadius: 999, padding: 6 }}>
<Icon.Alert className="i-14"/>
</div>
)}
</div>
<div style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 12, flex: 1 }}>
<div>
<div style={{ fontSize: 16, fontWeight: 600, lineHeight: 1.3, color: '#111827' }}>
{product.product_name}
</div>
<div style={{ fontSize: 13, color: '#6b7280', marginTop: 2 }}>{product.brands}</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, fontSize: 11 }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: '#9ca3af' }}>Energia:</span><span style={{ fontWeight: 600 }}>{product.nutriments.energy} kcal</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: '#9ca3af' }}>Gorduras:</span><span style={{ fontWeight: 600 }}>{product.nutriments.fat}g</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: '#9ca3af' }}>Açúcares:</span><span style={{ fontWeight: 600 }}>{product.nutriments.sugars}g</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: '#9ca3af' }}>Sódio:</span><span style={{ fontWeight: 600 }}>{product.nutriments.salt}g</span></div>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
{product.categories.split(',').slice(0,2).map(c => (
<span key={c} style={{ fontSize: 10, padding: '3px 8px', background: '#f9fafb', border: '1px solid #f3f4f6', borderRadius: 6, color: '#6b7280' }}>{c.trim()}</span>
))}
</div>
{product.allergens.length > 0 && (
<div style={{ fontSize: 11, color: '#ca8a04', display: 'flex', alignItems: 'center', gap: 4 }}>
<Icon.Alert className="i-14"/> Contém alérgenos
</div>
)}
<div style={{ display: 'flex', gap: 6, marginTop: 'auto', paddingTop: 6 }}>
<button style={{ flex: 1, padding: '10px', background: '#fff', border: '1px solid #e5e7eb', borderRadius: 8, fontSize: 13, fontWeight: 500, fontFamily: 'inherit', cursor: 'pointer' }}>
👁 Ver Detalhes
</button>
<button style={{ padding: '10px 12px', background: '#f3f4f6', border: '1px solid #e5e7eb', borderRadius: 8, fontSize: 14, fontFamily: 'inherit', cursor: 'pointer' }}>+</button>
</div>
</div>
</div>
);
}
function BeforeProductDetail({ product }) {
const gradeColors = { a: '#22c55e', b: '#4ade80', c: '#eab308', d: '#f97316', e: '#ef4444' };
return (
<div style={{ background: '#fff', fontFamily: 'Poppins, sans-serif', padding: 40, minHeight: '100%' }}>
<div style={{ fontSize: 11, color: '#6b7280', marginBottom: 18, fontFamily: 'ui-monospace, monospace', fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase' }}>
← Produto · {product.code}
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 40, marginBottom: 32 }}>
<div style={{ background: 'linear-gradient(135deg,#f9fafb,#f3f4f6)', borderRadius: 24, padding: 40, display: 'grid', placeItems: 'center', aspectRatio: '1/1' }}>
<ProductImage product={product} size={260}/>
</div>
<div>
<div style={{ fontSize: 11, color: '#2563eb', fontWeight: 900, letterSpacing: '0.2em', textTransform: 'uppercase', marginBottom: 10 }}>{product.brands}</div>
<h1 style={{ fontSize: 38, fontWeight: 900, letterSpacing: '-0.03em', lineHeight: 1.05, marginBottom: 20 }}>{product.product_name}</h1>
<div style={{ display: 'flex', gap: 14, marginBottom: 22 }}>
<div style={{ padding: '12px 18px', borderRadius: 14, background: gradeColors[product.nutrition_grade], color: '#fff', textAlign: 'center' }}>
<div style={{ fontSize: 10, fontWeight: 900, letterSpacing: '0.18em', textTransform: 'uppercase', opacity: 0.9 }}>Nutri-Score</div>
<div style={{ fontSize: 34, fontWeight: 900, lineHeight: 1 }}>{product.nutrition_grade.toUpperCase()}</div>
</div>
<div style={{ padding: '12px 18px', borderRadius: 14, background: '#111827', color: '#fff', textAlign: 'center' }}>
<div style={{ fontSize: 10, fontWeight: 900, letterSpacing: '0.18em', textTransform: 'uppercase', opacity: 0.8 }}>NOVA</div>
<div style={{ fontSize: 34, fontWeight: 900, lineHeight: 1 }}>{product.nova_group}</div>
</div>
</div>
<div style={{ padding: 20, background: '#f9fafb', borderRadius: 16, marginBottom: 18 }}>
<div style={{ fontSize: 11, color: '#6b7280', fontWeight: 900, letterSpacing: '0.2em', textTransform: 'uppercase', marginBottom: 12 }}>Composição / 100g</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, fontSize: 13 }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: '#6b7280' }}>Energia</span><span style={{ fontWeight: 700 }}>{product.nutriments.energy} kcal</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: '#6b7280' }}>Gorduras</span><span style={{ fontWeight: 700 }}>{product.nutriments.fat}g</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: '#6b7280' }}>Açúcares</span><span style={{ fontWeight: 700 }}>{product.nutriments.sugars}g</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: '#6b7280' }}>Sódio</span><span style={{ fontWeight: 700 }}>{product.nutriments.salt}g</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: '#6b7280' }}>Proteínas</span><span style={{ fontWeight: 700 }}>{product.nutriments.proteins}g</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: '#6b7280' }}>Fibras</span><span style={{ fontWeight: 700 }}>{product.nutriments.fiber}g</span></div>
</div>
</div>
{product.allergens.length > 0 && (
<div style={{ padding: 14, background: '#fef3c7', border: '1px solid #fde68a', borderRadius: 12, fontSize: 13, color: '#92400e', display: 'flex', alignItems: 'center', gap: 8 }}>
<Icon.Alert className="i-16"/> Contém: {product.allergens.join(', ')}
</div>
)}
</div>
</div>
</div>
);
}
Object.assign(window, { BeforeHero, BeforeFeatures, BeforeProductCard, BeforeProductDetail });
</script>
<script type="text/babel">
// AFTER — Revamped NutriScan landing
// Editorial, trustworthy, health-focused
function AfterHero({ onSearch }) {
const [q, setQ] = React.useState("");
return (
<section style={{ position: 'relative', padding: '96px 40px 80px', background: 'var(--ink-0)', overflow: 'hidden' }}>
{/* Subtle concentric arcs — organic, not grid */}
<svg aria-hidden style={{ position: 'absolute', right: '-200px', top: '40px', opacity: 0.35, pointerEvents: 'none' }} width="560" height="560" viewBox="0 0 560 560">
<defs>
<radialGradient id="fade" cx="50%" cy="50%">
<stop offset="0%" stopColor="var(--accent)" stopOpacity="0.25"/>
<stop offset="100%" stopColor="var(--accent)" stopOpacity="0"/>
</radialGradient>
</defs>
<circle cx="280" cy="280" r="260" fill="none" stroke="var(--ink-200)" strokeWidth="1"/>
<circle cx="280" cy="280" r="200" fill="none" stroke="var(--ink-200)" strokeWidth="1"/>
<circle cx="280" cy="280" r="140" fill="none" stroke="var(--ink-200)" strokeWidth="1"/>
<circle cx="280" cy="280" r="260" fill="url(#fade)"/>
</svg>
<div style={{ position: 'relative', maxWidth: 1000, margin: '0 auto' }}>
{/* Eyebrow */}
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 32 }}>
<span style={{ width: 8, height: 8, borderRadius: 999, background: 'var(--accent)' }}/>
<span className="eyebrow">Open Food Facts · 2M+ produtos</span>
</div>
{/* Headline — serif display, gentle italic for accent */}
<h1 className="display" style={{ color: 'var(--ink-900)', maxWidth: 900, marginBottom: 24 }}>
Entenda o que
<br/>
há dentro dos seus alimentos, <em style={{ color: 'var(--accent)', fontStyle: 'italic' }}>de verdade.</em>
</h1>
<p style={{
fontSize: 19, color: 'var(--ink-600)', maxWidth: 560, marginBottom: 44, lineHeight: 1.55,
}}>
Busque por nome ou código de barras e veja Nutri‑Score, grau de processamento
e alérgenos — sem rodeios, sem ads, de uma base colaborativa global.
</p>
{/* Search */}
<div style={{ maxWidth: 640 }}>
<div style={{
display: 'flex', alignItems: 'center', gap: 8,
background: 'var(--ink-0)', border: '1px solid var(--ink-200)',
borderRadius: 14, padding: 6,
boxShadow: '0 1px 2px rgba(0,0,0,0.03), 0 8px 24px -12px rgba(0,0,0,0.1)',
}}>
<div style={{ padding: '0 12px 0 14px', color: 'var(--ink-500)' }}>
<Icon.Search className="i-18"/>
</div>
<input
value={q}
onChange={e => setQ(e.target.value)}
onKeyDown={e => e.key === 'Enter' && onSearch?.(q)}
placeholder="Ex: Nutella, 7622210449283, aveia…"
style={{
flex: 1, border: 0, background: 'transparent',
padding: '14px 4px', fontSize: 16, color: 'var(--ink-900)', outline: 'none',
fontFamily: 'inherit',
}}
/>
<button className="btn btn-accent" onClick={() => onSearch?.(q)} style={{ padding: '12px 20px' }}>
Buscar
<Icon.ArrowRight className="i-16"/>
</button>
</div>
<div style={{ display: 'flex', gap: 16, marginTop: 14, fontSize: 13, color: 'var(--ink-500)', alignItems: 'center', flexWrap: 'wrap' }}>
<span className="label-sm">Tente:</span>
{['Oreo', 'Coca-Cola', 'Aveia Quaker'].map(s => (
<button key={s} onClick={() => { setQ(s); onSearch?.(s); }}
style={{ fontSize: 13, color: 'var(--ink-700)', borderBottom: '1px dashed var(--ink-300)', padding: '2px 0' }}>
{s}
</button>
))}
</div>
</div>
{/* Stats strip */}
<div style={{
marginTop: 72, paddingTop: 28, borderTop: '1px solid var(--ink-200)',
display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 32,
maxWidth: 720,
}}>
{[
{ n: '2M+', l: 'produtos catalogados' },
{ n: '100+', l: 'países cobertos' },
{ n: '100%', l: 'gratuito e open source' },
].map(s => (
<div key={s.l}>
<div style={{ fontFamily: 'var(--font-serif)', fontSize: 40, letterSpacing: '-0.02em', color: 'var(--ink-900)', lineHeight: 1 }}>{s.n}</div>
<div style={{ fontSize: 13, color: 'var(--ink-500)', marginTop: 6 }}>{s.l}</div>
</div>
))}
</div>
</div>
</section>
);
}
function AfterFeatures() {
const items = [
{ icon: Icon.Globe, title: 'Base colaborativa mundial', desc: 'Dados vivos do Open Food Facts, atualizados pela comunidade a cada minuto.' },
{ icon: Icon.Leaf, title: 'Sem interesses comerciais', desc: 'Sem anúncios, sem venda de dados. O que você vê é o que o rótulo diz.' },
{ icon: Icon.Shield, title: 'Alertas de alérgenos', desc: 'Identifica glúten, lactose, soja, amendoim e outros 12 grupos automaticamente.' },
{ icon: Icon.Book, title: 'Metodologia aberta', desc: 'Nutri‑Score e NOVA explicados em cada produto — sem caixa-preta.' },
];
return (
<section style={{ padding: '96px 40px', background: 'var(--ink-50)' }}>
<div style={{ maxWidth: 1000, margin: '0 auto' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 80, alignItems: 'start', marginBottom: 64 }}>
<div>
<div className="eyebrow" style={{ marginBottom: 16 }}>Princípios</div>
<h2 className="display-sm" style={{ color: 'var(--ink-900)' }}>
Um app de saúde merece <em style={{ fontStyle: 'italic', color: 'var(--accent)' }}>padrões de saúde.</em>
</h2>
</div>
<p style={{ fontSize: 16, color: 'var(--ink-600)', lineHeight: 1.6, paddingTop: 12 }}>
Cada decisão do NutriScan começa por uma pergunta simples:
isto torna a escolha alimentar mais clara? Se não torna, não entra.
</p>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 1, background: 'var(--ink-200)', borderRadius: 18, overflow: 'hidden', border: '1px solid var(--ink-200)' }}>
{items.map((f, i) => {
const I = f.icon;
return (
<div key={i} style={{ padding: 36, background: 'var(--ink-0)' }}>
<div style={{
width: 44, height: 44, borderRadius: 12,
background: 'var(--accent-soft)', color: 'var(--accent-ink)',
display: 'grid', placeItems: 'center', marginBottom: 20,
}}>
<I className="i-20"/>
</div>
<div style={{ fontFamily: 'var(--font-serif)', fontSize: 26, letterSpacing: '-0.01em', color: 'var(--ink-900)', marginBottom: 8 }}>
{f.title}
</div>
<div style={{ color: 'var(--ink-600)', fontSize: 14, lineHeight: 1.6 }}>{f.desc}</div>
</div>
);
})}
</div>
</div>
</section>
);
}
function AfterHowItWorks() {
const steps = [
{ n: '01', title: 'Pesquise', desc: 'Nome, marca ou código de barras. Detecção automática do tipo de busca.' },
{ n: '02', title: 'Consulte', desc: 'Conexão direta à base colaborativa Open Food Facts.' },
{ n: '03', title: 'Analise', desc: 'Nutri‑Score, NOVA e alertas de alérgenos em um só lugar.' },
{ n: '04', title: 'Decida', desc: 'Informação técnica, livre de vieses comerciais.' },
];
return (
<section style={{ padding: '96px 40px', background: 'var(--ink-0)' }}>
<div style={{ maxWidth: 1000, margin: '0 auto' }}>
<div style={{ marginBottom: 56, maxWidth: 640 }}>
<div className="eyebrow" style={{ marginBottom: 16 }}>Como funciona</div>
<h2 className="display-sm" style={{ color: 'var(--ink-900)' }}>
Quatro passos entre a dúvida e a clareza.
</h2>
</div>
<ol style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 0, position: 'relative' }}>
{/* Connector line */}
<div style={{ position: 'absolute', top: 36, left: '12%', right: '12%', height: 1, background: 'var(--ink-200)', zIndex: 0 }}/>
{steps.map((s, i) => (
<li key={s.n} style={{ position: 'relative', zIndex: 1, paddingRight: 20 }}>
<div style={{
width: 72, height: 72, borderRadius: '50%',
background: 'var(--ink-0)', border: '1px solid var(--ink-200)',
display: 'grid', placeItems: 'center',
fontFamily: 'var(--font-serif)', fontSize: 28, color: 'var(--accent)',
marginBottom: 24, letterSpacing: '-0.02em',
}}>
{s.n}
</div>
<div style={{ fontSize: 20, fontWeight: 500, letterSpacing: '-0.01em', color: 'var(--ink-900)', marginBottom: 6 }}>{s.title}</div>
<div style={{ fontSize: 14, color: 'var(--ink-600)', lineHeight: 1.55 }}>{s.desc}</div>
</li>
))}
</ol>
</div>
</section>
);
}
function AfterDonation() {
return (
<section style={{ padding: '96px 40px', background: 'var(--ink-900)', color: 'var(--ink-0)' }}>
<div style={{ maxWidth: 1000, margin: '0 auto', display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: 80, alignItems: 'center' }}>
<div>
<div style={{
fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '0.14em',
textTransform: 'uppercase', color: 'var(--ink-400)', marginBottom: 18,
}}>
<span style={{ display: 'inline-block', width: 8, height: 8, background: 'var(--accent)', borderRadius: 999, marginRight: 10, verticalAlign: 'middle' }}/>
Sistema on-line · 100% open source
</div>
<h2 className="display-sm" style={{ color: 'var(--ink-0)', marginBottom: 24 }}>
Mantenha o NutriScan <em style={{ fontStyle: 'italic', color: 'var(--accent)' }}>livre</em> — para sempre.
</h2>
<p style={{ fontSize: 16, color: 'var(--ink-500)', lineHeight: 1.65, marginBottom: 32, maxWidth: 480 }}>
Nascido em Garuva/SC, operado sem anúncios e sem venda de dados.
Toda contribuição vira infraestrutura, melhorias e mais horas de manutenção.
</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 420 }}>
{[
{ icon: Icon.Shield, t: 'Servidores e auditoria' },
{ icon: Icon.Globe, t: 'Expansão da base internacional' },
{ icon: Icon.Code, t: 'Código aberto em GitHub' },
].map((x, i) => {
const I = x.icon;
return (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '14px 0', borderTop: '1px solid var(--ink-300)' }}>
<I className="i-18" style={{ color: 'var(--accent)' }}/>
<span style={{ fontSize: 14, color: 'var(--ink-200)' }}>{x.t}</span>
</div>
);
})}
</div>
</div>
<div style={{
background: 'var(--ink-800)', borderRadius: 18, padding: 32,
border: '1px solid var(--ink-300)',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 24 }}>
<div style={{ width: 44, height: 44, borderRadius: 12, background: 'var(--accent-soft)', color: 'var(--accent-ink)', display: 'grid', placeItems: 'center' }}>
<Icon.Coffee className="i-20"/>
</div>
<div>
<div style={{ fontSize: 15, fontWeight: 500 }}>Apoie com um café</div>
<div style={{ fontSize: 11, color: 'var(--ink-500)', fontFamily: 'var(--font-mono)', letterSpacing: '0.1em', textTransform: 'uppercase' }}>qualquer valor ajuda</div>
</div>
</div>
<blockquote style={{
fontFamily: 'var(--font-serif)', fontSize: 20, lineHeight: 1.35,
color: 'var(--ink-100)', paddingLeft: 18, borderLeft: '2px solid var(--accent)',
marginBottom: 28,
}}>
“Cada contribuição me permite dedicar mais horas ao NutriScan — refatoração, dados e novas features.”
</blockquote>
<button className="btn btn-accent btn-lg" style={{ width: '100%' }}>
<Icon.Heart className="i-16"/>
Contribuir agora
</button>
<div style={{ marginTop: 18, fontSize: 11, color: 'var(--ink-500)', fontFamily: 'var(--font-mono)', letterSpacing: '0.14em', textTransform: 'uppercase', textAlign: 'center' }}>
Engenharia brasileira · Garuva/SC
</div>
</div>
</div>
</section>
);
}
Object.assign(window, { AfterHero, AfterFeatures, AfterHowItWorks, AfterDonation });
</script>
<script type="text/babel">
// AFTER — Product card with two densities + product detail page
// ------- COMPACT card — Nutri-Score dominant, minimal info
function AfterProductCardCompact({ product, onOpen }) {
return (
<article
onClick={() => onOpen?.(product)}
style={{
display: 'flex', gap: 14, alignItems: 'center',
padding: 14,
background: 'var(--ink-0)',
border: '1px solid var(--ink-200)',
borderRadius: 14,
cursor: 'pointer',
transition: 'all 0.18s',
}}
onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--ink-400)'; e.currentTarget.style.transform = 'translateY(-1px)'; }}
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--ink-200)'; e.currentTarget.style.transform = 'none'; }}
>
{/* Image thumb */}
<div style={{ flex: '0 0 72px', height: 72, borderRadius: 10, overflow: 'hidden', background: 'var(--ink-100)' }}>
<ProductImage product={product} size={72}/>
</div>
{/* Center content */}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{
fontSize: 11, color: 'var(--ink-500)',
fontFamily: 'var(--font-mono)', letterSpacing: '0.08em', textTransform: 'uppercase',
marginBottom: 2,
}}>
{product.brands}
</div>
<div style={{
fontSize: 15, fontWeight: 500, color: 'var(--ink-900)',
letterSpacing: '-0.005em',
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
marginBottom: 8,
}}>
{product.product_name}
</div>
<div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
<VerdictTag verdict={product.verdict}/>
<span style={{ color: 'var(--ink-300)' }}>·</span>
<span style={{ fontSize: 12, color: 'var(--ink-500)', fontFamily: 'var(--font-mono)' }}>NOVA {product.nova_group}</span>
{product.allergens.length > 0 && (<>
<span style={{ color: 'var(--ink-300)' }}>·</span>
<span style={{ fontSize: 12, color: 'var(--warn)', display: 'inline-flex', alignItems: 'center', gap: 4 }}>
<Icon.Alert className="i-14"/> {product.allergens.length} alérgeno{product.allergens.length > 1 ? 's' : ''}
</span>
</>)}
</div>
</div>
{/* Nutri-Score — the hero metric */}
<div style={{ flex: '0 0 auto' }}>
<NutriLetter grade={product.nutrition_grade} size={64}/>
</div>
</article>
);
}
// ------- EXPANDED card — full info, portrait orientation
function AfterProductCardExpanded({ product, onOpen }) {
return (
<article
onClick={() => onOpen?.(product)}
style={{
display: 'flex', flexDirection: 'column',
background: 'var(--ink-0)',
border: '1px solid var(--ink-200)',
borderRadius: 16,
overflow: 'hidden',
cursor: 'pointer',
transition: 'all 0.2s',
}}
onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--ink-400)'; e.currentTarget.style.transform = 'translateY(-2px)'; e.currentTarget.style.boxShadow = '0 12px 32px -14px rgba(0,0,0,0.15)'; }}
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--ink-200)'; e.currentTarget.style.transform = 'none'; e.currentTarget.style.boxShadow = 'none'; }}
>
{/* Hero image area w/ Nutri-Score floating */}
<div style={{ position: 'relative', aspectRatio: '5/4', background: 'var(--ink-50)', borderBottom: '1px solid var(--ink-200)' }}>
<div style={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center', padding: 24 }}>
<ProductImage product={product} size={180}/>
</div>
{/* Nutri-Score corner */}
<div style={{ position: 'absolute', top: 14, right: 14 }}>
<NutriLetter grade={product.nutrition_grade} size={56}/>
</div>
{/* Allergen indicator */}
{product.allergens.length > 0 && (
<div style={{ position: 'absolute', top: 14, left: 14 }}>
<span className="chip warn" style={{ padding: '4px 9px', fontSize: 11 }}>
<Icon.Alert className="i-14"/> {product.allergens.length}
</span>
</div>
)}
</div>
{/* Meta */}
<div style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 14, flex: 1 }}>
<div>
<div style={{
fontSize: 11, color: 'var(--ink-500)',
fontFamily: 'var(--font-mono)', letterSpacing: '0.08em', textTransform: 'uppercase',
marginBottom: 4,
}}>
{product.brands}
</div>
<h3 style={{
fontFamily: 'var(--font-serif)', fontSize: 22,
letterSpacing: '-0.01em', lineHeight: 1.15,
color: 'var(--ink-900)',
display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical',
overflow: 'hidden',
}}>
{product.product_name}
</h3>
</div>
{/* Nutriments mini-bar */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 0, borderTop: '1px solid var(--ink-200)', borderBottom: '1px solid var(--ink-200)' }}>
{[
{ l: 'kcal', v: product.nutriments.energy },
{ l: 'Açúcar', v: `${product.nutriments.sugars}g` },
{ l: 'Gord.', v: `${product.nutriments.fat}g` },
{ l: 'Sódio', v: `${product.nutriments.salt}g` },
].map((n, i) => (
<div key={n.l} style={{
padding: '12px 6px',
borderRight: i < 3 ? '1px solid var(--ink-200)' : 'none',
textAlign: 'center',
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
minWidth: 0,
}}>
<div style={{
fontFamily: 'var(--font-serif)', fontSize: 18, color: 'var(--ink-900)', lineHeight: 1.1,
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '100%',
}}>{n.v}</div>
<div style={{
fontSize: 10, color: 'var(--ink-500)', fontFamily: 'var(--font-mono)',
letterSpacing: '0.08em', textTransform: 'uppercase', marginTop: 6,
}}>{n.l}</div>
</div>
))}
</div>
{/* Footer row */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 'auto' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<VerdictTag verdict={product.verdict}/>
</div>
<div style={{ fontSize: 12, color: 'var(--ink-500)', fontFamily: 'var(--font-mono)' }}>
NOVA {product.nova_group}
</div>
</div>
</div>
</article>
);
}
// ------- Density toggle
function DensityToggle({ mode, setMode }) {
return (
<div style={{
display: 'inline-flex',
padding: 3,
background: 'var(--ink-100)',
border: '1px solid var(--ink-200)',
borderRadius: 10,
}}>
{[
{ id: 'compact', icon: Icon.List, label: 'Compacto' },
{ id: 'expanded', icon: Icon.Grid, label: 'Cards' },
].map(opt => {
const I = opt.icon;
return (
<button
key={opt.id}
onClick={() => setMode(opt.id)}
style={{
display: 'inline-flex', alignItems: 'center', gap: 8,
padding: '7px 12px', borderRadius: 7,
fontSize: 13, fontWeight: 500,
background: mode === opt.id ? 'var(--ink-0)' : 'transparent',
color: mode === opt.id ? 'var(--ink-900)' : 'var(--ink-500)',
boxShadow: mode === opt.id ? '0 1px 2px rgba(0,0,0,0.05)' : 'none',
transition: 'all 0.15s',
}}
>
<I className="i-14"/>
{opt.label}
</button>
);
})}
</div>
);
}
// ------- Results page shell — interactive grid/list with filters
function AfterResultsPage() {
const [density, setDensity] = React.useState('expanded');
const [sortBy, setSortBy] = React.useState('score');
const [query, setQuery] = React.useState('');
const [filters, setFilters] = React.useState({ grade: null, allergen: null });
let list = [...window.SAMPLE_PRODUCTS];
if (query) list = list.filter(p => (p.product_name + ' ' + p.brands).toLowerCase().includes(query.toLowerCase()));
if (filters.grade) list = list.filter(p => p.nutrition_grade === filters.grade);
if (filters.allergen === 'free') list = list.filter(p => p.allergens.length === 0);
if (sortBy === 'score') list.sort((a, b) => a.nutrition_grade.localeCompare(b.nutrition_grade));
if (sortBy === 'sugar') list.sort((a, b) => (a.nutriments.sugars || 0) - (b.nutriments.sugars || 0));
if (sortBy === 'name') list.sort((a, b) => a.product_name.localeCompare(b.product_name));
return (
<div style={{ background: 'var(--ink-0)', padding: '32px 40px 60px', minHeight: '100%' }}>
{/* Top search bar */}
<div style={{ marginBottom: 28 }}>
<div style={{
display: 'flex', alignItems: 'center', gap: 8,
background: 'var(--ink-0)', border: '1px solid var(--ink-200)',
borderRadius: 12, padding: 4, maxWidth: 520,
}}>
<div style={{ padding: '0 10px 0 12px', color: 'var(--ink-500)' }}>
<Icon.Search className="i-16"/>
</div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Buscar produto, marca ou código…"
style={{ flex: 1, border: 0, background: 'transparent', padding: '10px 4px', fontSize: 14, outline: 'none', color: 'var(--ink-900)', fontFamily: 'inherit' }}
/>
<button className="btn btn-primary btn-sm" style={{ padding: '8px 14px' }}>Buscar</button>
</div>
</div>
{/* Results header */}
<div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', marginBottom: 24, flexWrap: 'wrap', gap: 16 }}>
<div>
<div className="eyebrow" style={{ marginBottom: 6 }}>{list.length} resultados</div>
<h2 className="display-sm" style={{ fontSize: 34 }}>
{query ? <>Para <em style={{ fontStyle: 'italic', color: 'var(--accent)' }}>"{query}"</em></> : 'Todos os produtos'}
</h2>
</div>
<DensityToggle mode={density} setMode={setDensity}/>
</div>
{/* Filter strip */}
<div style={{ display: 'flex', alignItems: 'center', gap: 20, padding: '14px 0', borderTop: '1px solid var(--ink-200)', borderBottom: '1px solid var(--ink-200)', marginBottom: 28, flexWrap: 'wrap' }}>
<span className="label-sm">Filtrar</span>
<div style={{ display: 'flex', gap: 6 }}>
{['a','b','c','d','e'].map(g => (
<button
key={g}
onClick={() => setFilters(f => ({ ...f, grade: f.grade === g ? null : g }))}
style={{
width: 28, height: 28, borderRadius: 999,
fontFamily: 'var(--font-sans)', fontSize: 11, fontWeight: 700,
background: filters.grade === g
? `var(--nutri-${g})`
: 'var(--ink-100)',
color: filters.grade === g ? (g === 'c' ? '#3a2a00' : '#fff') : 'var(--ink-600)',
border: filters.grade === g ? 'none' : '1px solid var(--ink-200)',
transition: 'all 0.15s',
}}
>{g.toUpperCase()}</button>
))}
</div>
<button
onClick={() => setFilters(f => ({ ...f, allergen: f.allergen === 'free' ? null : 'free' }))}
className="chip"
style={{
background: filters.allergen === 'free' ? 'var(--accent-soft)' : 'var(--ink-100)',
color: filters.allergen === 'free' ? 'var(--accent-ink)' : 'var(--ink-700)',
borderColor: filters.allergen === 'free' ? 'var(--accent)' : 'var(--ink-200)',
cursor: 'pointer',
}}
>
<Icon.Leaf className="i-14"/> Sem alérgenos comuns
</button>
<div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 10 }}>
<span className="label-sm">Ordenar</span>
<select
value={sortBy} onChange={e => setSortBy(e.target.value)}
style={{
border: '1px solid var(--ink-200)', borderRadius: 8, padding: '6px 28px 6px 10px',
background: 'var(--ink-0)', color: 'var(--ink-900)', fontSize: 13, fontFamily: 'inherit',
appearance: 'none',
backgroundImage: `url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%237a7269' fill='none' stroke-linecap='round'/%3E%3C/svg%3E")`,
backgroundRepeat: 'no-repeat', backgroundPosition: 'right 10px center',
}}
>
<option value="score">Nutri‑Score</option>
<option value="sugar">Menos açúcar</option>
<option value="name">Nome (A–Z)</option>
</select>
</div>
</div>
{/* List/grid */}
{density === 'compact' ? (
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
{list.map(p => <AfterProductCardCompact key={p.code} product={p}/>)}
</div>
) : (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: 20 }}>
{list.map(p => <AfterProductCardExpanded key={p.code} product={p}/>)}
</div>
)}
{list.length === 0 && (
<div style={{ padding: 60, textAlign: 'center', color: 'var(--ink-500)' }}>
<div style={{ fontFamily: 'var(--font-serif)', fontSize: 28, color: 'var(--ink-700)', marginBottom: 8 }}>Nada encontrado</div>
<div style={{ fontSize: 14 }}>Tente limpar os filtros ou usar outros termos.</div>
</div>
)}
</div>
);
}
// ------- Product detail page
function AfterProductDetail({ product }) {
const gradeDesc = { a: 'Excelente qualidade nutricional', b: 'Boa qualidade nutricional', c: 'Qualidade regular', d: 'Qualidade ruim', e: 'Qualidade muito ruim' }[product.nutrition_grade];
const novaDesc = {
1: 'Alimento in natura ou minimamente processado.',
2: 'Ingrediente culinário processado.',
3: 'Alimento processado com adição de sal, açúcar ou óleo.',
4: 'Ultraprocessado — formulação industrial com aditivos.',
}[product.nova_group];
// For nutriment bars — % of reference daily intake (simplified)
const dailyRef = { energy: 2000, fat: 70, sat_fat: 20, sugars: 90, salt: 6, fiber: 30, proteins: 50 };
const bar = (key) => Math.min(100, Math.round((product.nutriments[key] / dailyRef[key]) * 100));
return (
<div style={{ background: 'var(--ink-0)', minHeight: '100%' }}>
{/* Top bar */}
<div style={{
padding: '18px 40px', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
borderBottom: '1px solid var(--ink-200)',
}}>
<button style={{ display: 'inline-flex', alignItems: 'center', gap: 10, fontSize: 13, color: 'var(--ink-700)' }}>
<Icon.Back className="i-16"/> Voltar
</button>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--ink-500)', letterSpacing: '0.12em' }}>
EAN · {product.code}
</div>
<div style={{ display: 'flex', gap: 8 }}>
<button className="btn btn-ghost btn-sm"><Icon.Heart className="i-14"/></button>
<button className="btn btn-ghost btn-sm"><Icon.Share className="i-14"/></button>
</div>
</div>
{/* Hero: image + headline + nutri-score */}
<div style={{ padding: '40px 40px 32px', display: 'grid', gridTemplateColumns: '1fr 1.2fr', gap: 48 }}>
<div style={{ background: 'var(--ink-50)', borderRadius: 18, aspectRatio: '1/1', display: 'grid', placeItems: 'center', border: '1px solid var(--ink-200)' }}>
<ProductImage product={product} size={280}/>
</div>
<div>
<div style={{ fontSize: 12, color: 'var(--ink-500)', fontFamily: 'var(--font-mono)', letterSpacing: '0.12em', textTransform: 'uppercase', marginBottom: 12 }}>
{product.brands}
</div>
<h1 className="display-sm" style={{ fontSize: 44, marginBottom: 24 }}>{product.product_name}</h1>
{/* Hero metric: Nutri-Score big */}
<div style={{
display: 'flex', alignItems: 'center', gap: 20, padding: 24,
background: 'var(--ink-50)', border: '1px solid var(--ink-200)', borderRadius: 16,
marginBottom: 16,
}}>
<NutriLetter grade={product.nutrition_grade} size={92}/>
<div style={{ flex: 1 }}>
<div className="label-sm" style={{ marginBottom: 4 }}>Nutri‑Score</div>
<div style={{ fontFamily: 'var(--font-serif)', fontSize: 28, letterSpacing: '-0.01em', color: 'var(--ink-900)', lineHeight: 1.1, marginBottom: 10 }}>
{gradeDesc}
</div>
<NutriScale active={product.nutrition_grade} size="sm"/>
</div>
</div>
{/* NOVA row */}
<div style={{
padding: 20, border: '1px solid var(--ink-200)', borderRadius: 14, marginBottom: 16,
display: 'flex', gap: 18, alignItems: 'center',
}}>
<NovaPill group={product.nova_group}/>
<div style={{ fontSize: 14, color: 'var(--ink-600)', lineHeight: 1.5, flex: 1 }}>
{novaDesc}
</div>
</div>
{/* Allergens */}
{product.allergens.length > 0 && (
<div style={{
padding: 18, borderRadius: 14,
background: 'var(--warn-soft)', border: '1px solid #fcd34d',
display: 'flex', alignItems: 'flex-start', gap: 14,
}}>
<Icon.Alert className="i-20" style={{ color: 'var(--warn)', flexShrink: 0, marginTop: 1 }}/>
<div>
<div style={{ fontSize: 13, fontWeight: 600, color: 'var(--warn)', marginBottom: 4 }}>
Contém alérgenos
</div>
<div style={{ fontSize: 14, color: '#78350f' }}>
{product.allergens.map(a => ({ gluten: 'Glúten', milk: 'Leite', soy: 'Soja', nuts: 'Oleaginosas', eggs: 'Ovos', peanuts: 'Amendoim' }[a] || a)).join(' · ')}
</div>
</div>
</div>
)}
</div>
</div>
{/* Nutriments section */}
<div style={{ padding: '32px 40px 60px', borderTop: '1px solid var(--ink-200)' }}>
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 20 }}>
<h3 className="display-sm" style={{ fontSize: 28 }}>Composição nutricional</h3>
<div className="label-sm">por 100g · %VD de referência</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '8px 48px' }}>
{[
{ k: 'energy', l: 'Energia', u: 'kcal' },
{ k: 'fat', l: 'Gorduras totais', u: 'g' },
{ k: 'sat_fat', l: 'Gorduras saturadas', u: 'g' },
{ k: 'sugars', l: 'Açúcares', u: 'g' },
{ k: 'salt', l: 'Sódio', u: 'g' },
{ k: 'fiber', l: 'Fibras', u: 'g' },
{ k: 'proteins',l: 'Proteínas', u: 'g' },
].map(row => {
const pct = bar(row.k);
const v = product.nutriments[row.k];
const high = pct > 40 && ['fat','sat_fat','sugars','salt'].includes(row.k);
const good = ['fiber','proteins'].includes(row.k);
return (
<div key={row.k} style={{ padding: '14px 0', borderBottom: '1px solid var(--ink-200)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 8 }}>
<span style={{ fontSize: 14, color: 'var(--ink-800)' }}>{row.l}</span>
<span style={{ fontFamily: 'var(--font-serif)', fontSize: 18, color: 'var(--ink-900)' }}>
{v}<span style={{ fontSize: 12, color: 'var(--ink-500)', marginLeft: 3, fontFamily: 'var(--font-mono)' }}>{row.u}</span>
</span>
</div>
<div style={{ height: 4, background: 'var(--ink-100)', borderRadius: 999, overflow: 'hidden', position: 'relative' }}>
<div style={{
width: `${pct}%`, height: '100%',
background: high ? 'var(--nutri-e)' : good ? 'var(--accent)' : 'var(--ink-500)',
transition: 'width 0.6s',
}}/>
</div>
<div style={{ fontSize: 11, color: 'var(--ink-500)', marginTop: 4, fontFamily: 'var(--font-mono)', letterSpacing: '0.04em' }}>
{pct}% VD
</div>
</div>
);
})}
</div>
</div>
{/* Footer */}
<div style={{ padding: '28px 40px', background: 'var(--ink-50)', borderTop: '1px solid var(--ink-200)' }}>
<div style={{ fontSize: 12, color: 'var(--ink-500)', lineHeight: 1.6, maxWidth: 700 }}>
Dados fornecidos pela comunidade Open Food Facts sob licença ODbL.
Última atualização: há 3 dias · fonte: contribuidor verificado.
</div>
</div>
</div>
);
}
Object.assign(window, {
AfterProductCardCompact, AfterProductCardExpanded,
DensityToggle, AfterResultsPage, AfterProductDetail,
});
</script>
<script type="text/babel" data-presets="react">
const { useState, useEffect } = React;
const SECTIONS = [
{ id: 'hero', label: 'Hero' },
{ id: 'features', label: 'Features' },
{ id: 'detail', label: 'Produto' },
{ id: 'results', label: 'Resultados' },
{ id: 'donation', label: 'Doação' },
];
function Frame({ label, children, kind, tall }) {
return (
<div className="compare-frame" style={{ display: 'flex', flexDirection: 'column' }}>
<div className={`compare-label ${kind === 'new' ? 'new' : ''}`}>
{kind === 'new' ? 'Depois · Proposta' : 'Antes · Atual em produção'}
</div>
<div className={`frame-body${tall ? ' tall' : ''}`}>
{children}
</div>
</div>
);
}
function Rationale({ items }) {
return (
<div className="rationale">
{items.map((it, i) => (
<div key={i}>
<span className="tag">{it.tag}</span>
<h4>{it.title}</h4>
<p>{it.desc}</p>
</div>
))}
</div>
);
}
function App() {
const [theme, setTheme] = useState(() => localStorage.getItem('nsc-theme') || 'light');
const [section, setSection] = useState(() => localStorage.getItem('nsc-section') || 'hero');
useEffect(() => {
document.documentElement.className = theme === 'dark' ? 'theme-dark' : '';
localStorage.setItem('nsc-theme', theme);
}, [theme]);
useEffect(() => { localStorage.setItem('nsc-section', section); }, [section]);
const sampleProduct = window.SAMPLE_PRODUCTS[1]; // Nutella
return (
<div className="shell">
<header className="shell-head">
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 10 }}>
<div style={{
width: 36, height: 36, borderRadius: 10,
background: 'var(--accent)', color: '#fff',
display: 'grid', placeItems: 'center',
fontFamily: 'var(--font-serif)', fontSize: 22,
}}>N</div>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--ink-500)', letterSpacing: '0.14em', textTransform: 'uppercase' }}>
Design Review · Abril 2026
</div>
</div>
<h1 className="shell-title">NutriScan — revamp de design</h1>
<p className="shell-subtitle">
Comparativo antes / depois das principais superfícies do produto.
Tipografia editorial (Instrument Serif + Inter), paleta neutra quente com um único accent verde,
e o Nutri‑Score como métrica herói. Todos os exemplos abaixo são interativos.
</p>
</div>
<div style={{ display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }}>
<div className="tabs">
{SECTIONS.map(s => (
<button key={s.id} onClick={() => setSection(s.id)} className={section === s.id ? 'active' : ''}>
{s.label}
</button>
))}
</div>
<button className="theme-switch" onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>
{theme === 'dark' ? <Icon.Sun className="i-14"/> : <Icon.Moon className="i-14"/>}
{theme === 'dark' ? 'Light' : 'Dark'}
</button>
</div>
</header>
{section === 'hero' && (
<>
<div className="section-title">Hero <small>Landing · página inicial</small></div>
<Rationale items={[
{ tag: 'Tipografia', title: 'Serif editorial no headline', desc: 'Instrument Serif sinaliza confiança e leitura — em vez de tech-bravado. Peso 400 + itálico para acento.' },
{ tag: 'Tom', title: 'Linguagem direta, humana', desc: '"Entenda o que há dentro" substitui "Descriptografamos rótulos para decisões soberanas". Menos jargão, mais clareza.' },
{ tag: 'Cor', title: 'Um único accent, bem usado', desc: 'Verde clínico oklch(52% 0.11 155) no elemento mais importante. Gradientes e badges tech removidos.' },
]}/>
<div className="grid-2">
<Frame kind="old"><BeforeHero/></Frame>
<Frame kind="new"><AfterHero onSearch={() => setSection('results')}/></Frame>
</div>
</>
)}
{section === 'features' && (
<>
<div className="section-title">Features + How it works <small>Princípios &amp; fluxo</small></div>
<Rationale items={[
{ tag: 'Hierarquia', title: 'Do grid de cartões ao sistema', desc: '4 cards coloridos foram substituídos por uma "planilha" de 2×2 com hairline shared — mais editorial, menos ruidoso.' },
{ tag: 'Ícones', title: 'Mesma família, mesmo peso', desc: 'Ícones monocromáticos em um único accent. Antes: cada feature tinha uma cor própria, diluindo a leitura.' },
{ tag: 'Fluxo', title: 'Passos com linha conectora', desc: 'Números serifados em círculos outline guiam o olho. Sem "STEP_01" techy.' },
]}/>
<div className="grid-2">
<Frame kind="old" tall>
<BeforeFeatures/>
</Frame>
<Frame kind="new" tall>
<AfterFeatures/>
<AfterHowItWorks/>
</Frame>
</div>
</>
)}
{section === 'detail' && (
<>
<div className="section-title">Página de produto <small>superfície mais crítica</small></div>
<Rationale items={[
{ tag: 'Métrica-herói', title: 'Nutri-Score dominante', desc: 'Letra grande (92px) num círculo oficial da cor correspondente, com a escala A–E completa logo abaixo para contexto.' },
{ tag: 'Clareza', title: 'Composição com barras VD', desc: 'Cada nutriente recebe uma barra proporcional ao % do valor diário de referência, com verde/vermelho semânticos.' },
{ tag: 'Trust', title: 'Atribuição visível', desc: 'Fonte e data da informação são mostradas no rodapé. Saúde pede transparência da procedência.' },
]}/>
<div className="grid-2">
<Frame kind="old" tall><BeforeProductDetail product={sampleProduct}/></Frame>
<Frame kind="new" tall><AfterProductDetail product={sampleProduct}/></Frame>
</div>
</>
)}
{section === 'results' && (
<>
<div className="section-title">Grid / Lista de resultados <small>compact + expanded toggle</small></div>
<Rationale items={[
{ tag: 'Densidade', title: 'Dois modos com alternância', desc: 'Cards para exploração (imagem grande), Compact para triagem rápida (mais 4× por tela). O toggle persiste a preferência.' },
{ tag: 'Nutri-Score', title: 'Hero consistente', desc: 'Letra circular aparece nos DOIS modos, com o mesmo estilo visual da página de produto. Linguagem unificada.' },
{ tag: 'Filtros', title: 'Em linha, não sidebar', desc: 'Badges coloridas A–E agem como filtro inline — acionáveis e didáticas ao mesmo tempo.' },
]}/>
<div className="grid-2" style={{ marginBottom: 20 }}>
<Frame kind="old" tall>
<div style={{ padding: 24, background: '#fff' }}>
<div style={{ fontFamily: 'Poppins, sans-serif', fontSize: 24, fontWeight: 800, marginBottom: 20 }}>
Resultados <span style={{ color: '#2563eb' }}>(atual)</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 16 }}>
{window.SAMPLE_PRODUCTS.slice(0, 4).map(p => <BeforeProductCard key={p.code} product={p}/>)}
</div>
</div>
</Frame>
<Frame kind="new" tall>
<AfterResultsPage/>
</Frame>
</div>
</>
)}
{section === 'donation' && (
<>
<div className="section-title">Seção de doação <small>closing</small></div>
<Rationale items={[
{ tag: 'Ancoragem', title: 'Fundo escuro intencional', desc: 'A mudança de tom reforça "parada para ação". Antes era mais um bloco claro em uma parede de branco.' },
{ tag: 'Copy', title: 'Valor antes do pedido', desc: 'Primeiro o porquê (sem anúncios, sem venda de dados), depois o CTA. Quote em serif para humanizar.' },
{ tag: 'Conexão', title: 'Conecta com o hero', desc: 'Usa o mesmo accent verde, a mesma tipografia — consistência visual entre seções que antes pareciam apps diferentes.' },
]}/>
<div className="full">
<Frame kind="new" tall><AfterDonation/></Frame>
</div>
</>
)}
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>
</body></html>