feat(ui): ReplyPier 品牌、中文界面与前端构建产物

替换品牌文案与图标,补齐窄屏布局和加载失败提示,确认主要旅程可操作。
This commit is contained in:
WangShen007 committed 2026-10-15 20:25:00 +08:00
1 parent bf315b85ca
commit 69a71aabbb
77 files changed
+2916

No files matched your search

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="24px" height="24px" fill-rule="nonzero"><g fill="#000000" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(10.66667,10.66667)"><path d="M18,21h-12c-0.6,0 -1,-0.4 -1,-1v-8c0,-1.7 1.3,-3 3,-3h8c1.7,0 3,1.3 3,3v8c0,0.6 -0.4,1 -1,1z" opacity="0.3"></path><path d="M12,2c-1.10457,0 -2,0.89543 -2,2c0,1.10457 0.89543,2 2,2c1.10457,0 2,-0.89543 2,-2c0,-1.10457 -0.89543,-2 -2,-2zM9.5,13c-0.82843,0 -1.5,0.67157 -1.5,1.5c0,0.82843 0.67157,1.5 1.5,1.5c0.82843,0 1.5,-0.67157 1.5,-1.5c0,-0.82843 -0.67157,-1.5 -1.5,-1.5zM14.5,13c-0.82843,0 -1.5,0.67157 -1.5,1.5c0,0.82843 0.67157,1.5 1.5,1.5c0.82843,0 1.5,-0.67157 1.5,-1.5c0,-0.82843 -0.67157,-1.5 -1.5,-1.5z"></path><path d="M11,4h2v6h-2z"></path><path d="M18,22h-12c-1.1,0 -2,-0.9 -2,-2v-8c0,-2.2 1.8,-4 4,-4h8c2.2,0 4,1.8 4,4v8c0,1.1 -0.9,2 -2,2zM18,20v1v-1zM8,10c-1.1,0 -2,0.9 -2,2v8h12v-8c0,-1.1 -0.9,-2 -2,-2z"></path><path d="M9,18h6v3h-6zM2,16h3v2h-3zM19,16h3v2h-3z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 3V19C3 20.1046 3.89543 21 5 21H21" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<path d="M7 14L11 10L15 14L21 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="7" cy="14" r="2" fill="currentColor"/>
<circle cx="11" cy="10" r="2" fill="currentColor"/>
<circle cx="15" cy="14" r="2" fill="currentColor"/>
<circle cx="21" cy="8" r="2" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 592 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 12H5M5 12L12 19M5 12L12 5" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 226 B

+10
View File
@@ -0,0 +1,10 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Background Circle -->
<circle cx="20" cy="20" r="20" fill="#f34611"/>
<!-- Head -->
<circle cx="20" cy="16" r="7" fill="white"/>
<!-- Body -->
<path d="M20 25C15.8 25 8 27.2 8 31.4V35H32V31.4C32 27.2 24.2 25 20 25Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 50 50">
<path d="M 25 4.5 C 15.204 4.5 5.9439688 11.985969 3.9179688 21.542969 C 3.9119687 21.571969 3.9200156 21.599906 3.9160156 21.628906 C 1.5620156 23.233906 -0.04296875 26.383 -0.04296875 30 C -0.04296875 35.238 3.3210312 39.5 7.4570312 39.5 C 7.7850313 39.5 8.0913438 39.339313 8.2773438 39.070312 C 8.4643437 38.800312 8.5065781 38.456438 8.3925781 38.148438 C 8.3775781 38.110438 6.9550781 34.244 6.9550781 29.5 C 6.9550781 24.506 8.3091719 22.022187 8.3261719 21.992188 C 8.5011719 21.683187 8.4983125 21.305047 8.3203125 20.998047 C 8.1433125 20.689047 7.8130313 20.5 7.4570312 20.5 C 7.0350313 20.5 6.62275 20.554625 6.21875 20.640625 C 8.58675 12.613625 16.57 6.5 25 6.5 C 32.992 6.5 40.688641 12.044172 43.431641 19.576172 C 43.133641 19.530172 42.831438 19.5 42.523438 19.5 C 42.169438 19.5 41.841109 19.689094 41.662109 19.996094 C 41.482109 20.302094 41.481297 20.683187 41.654297 20.992188 C 41.668297 21.016188 43.023437 23.5 43.023438 28.5 C 43.023438 32.44 42.045078 35.767641 41.705078 36.806641 C 40.558078 37.740641 38.815344 39.034297 36.777344 40.154297 C 36.016344 39.305297 34.839391 38.871437 33.650391 39.148438 L 31.867188 39.560547 C 31.024188 39.753547 30.308609 40.262094 29.849609 40.996094 C 29.391609 41.728094 29.245453 42.598453 29.439453 43.439453 C 29.783453 44.935453 31.11975 45.949219 32.59375 45.949219 C 32.83275 45.949219 33.074359 45.923188 33.318359 45.867188 L 35.103516 45.457031 C 35.945516 45.264031 36.661141 44.752531 37.119141 44.019531 C 37.503141 43.406531 37.653984 42.700187 37.583984 41.992188 C 39.728984 40.830188 41.570453 39.483422 42.814453 38.482422 C 46.814453 38.286422 50.023438 34.114 50.023438 29 C 50.023438 25.237 48.284437 21.989172 45.773438 20.451172 C 45.769438 20.376172 45.777859 20.301563 45.755859 20.226562 C 43.152859 11.113563 34.423 4.5 25 4.5 z M 12 19 C 11.447 19 11 19.447 11 20 L 11 32 C 11 32.553 11.447 33 12 33 L 28.044922 33 C 27.540922 34.057 26.743578 35.482375 26.142578 36.484375 C 25.941578 36.819375 25.954828 37.2405 26.173828 37.5625 C 26.360828 37.8395 26.673 38 27 38 C 27.055 38 27.109063 37.995328 27.164062 37.986328 C 33.351062 36.955328 38.412 32.95125 38.625 32.78125 C 38.862 32.59125 39 32.304 39 32 L 39 20 C 39 19.447 38.553 19 38 19 L 12 19 z M 18.5 23 C 19.902 23 21 24.317 21 26 C 21 27.683 19.902 29 18.5 29 C 17.098 29 16 27.683 16 26 C 16 24.317 17.098 23 18.5 23 z M 31.5 23 C 32.902 23 34 24.317 34 26 C 34 27.683 32.902 29 31.5 29 C 30.098 29 29 27.683 29 26 C 29 24.317 30.098 23 31.5 23 z"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+119
View File
@@ -0,0 +1,119 @@
/* ReplyPier base — global resets & keyframes */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--background-base-rgb: 11,12,16;
--background-soft-rgb: 14,15,20;
--background-dark-rgb: 0,0,0;
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
html, body {
min-height: 100vh;
background: var(--bg);
color: var(--text);
}
body {
line-height: 1.6;
font-family: var(--font-sans);
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Display headings */
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-display);
font-weight: var(--font-weight-bold);
letter-spacing: var(--tracking-display);
color: var(--text);
line-height: 1.2;
}
/* Inputs inherit font */
input, textarea, select, button {
font-family: var(--font-sans);
color: var(--text);
}
input::placeholder, textarea::placeholder {
color: var(--faint);
}
/* Text selection */
::selection {
background: #C9F24E;
color: #0B0C10;
}
[data-theme="light"] ::selection {
background: #4C7A0B;
color: #ffffff;
}
/* Scrollbars */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--o10);
border-radius: 6px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--o16);
}
/* Global focus ring */
:focus-visible {
outline: none;
box-shadow: var(--ring-focus);
border-radius: var(--radius-sm);
}
/* Keyframe animations */
@keyframes rp-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes rp-pulse {
0%, 100% { opacity: .4; }
50% { opacity: 1; }
}
@keyframes rp-aurora {
0% { transform: translate3d(-4%,-2%,0) scale(1); }
50% { transform: translate3d(4%,3%,0) scale(1.12); }
100% { transform: translate3d(-4%,-2%,0) scale(1); }
}
@keyframes rp-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-12px); }
}
@keyframes button-loading-spinner {
from { transform: rotate(0turn); }
to { transform: rotate(1turn); }
}
+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#9CA3AF">
<path d="M12 2C6.477 2 2 5.582 2 10c0 2.136 1.01 4.08 2.636 5.546.139.127.22.31.22.504v3.45l4.329-2.164c.175-.088.37-.135.568-.135h.494C11.447 17.73 12.947 18 14.5 18c5.523 0 10-3.582 10-8s-4.477-8-10-8zm0 14c-1.105 0-2-.895-2-2s.895-2 2-2 2 .895 2 2-.895 2-2 2z"/>
</svg>

After

Width:  |  Height:  |  Size: 351 B

+1
View File
@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Amazon CloudWatch</title><rect x="1" y="1" width="22" height="22" rx="4.5" fill="#E7157B"/><path d="M3.6 13.2h3.4l1.9-4.9 2.7 7.4 1.9-4.6 1.2 2.1h5.7" fill="none" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 329 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="50px" height="50px" fill-rule="nonzero"><g fill="#000000" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(5.12,5.12)"><path d="M41.5,0c-4.68248,0 -8.5,3.81752 -8.5,8.5c0,0.98423 0.29088,1.87869 0.59961,2.75586l-7.53516,7.5332c-1.41239,1.41239 -1.41239,3.73214 0,5.14453c1.41239,1.41239 3.7341,1.41239 5.14648,0l7.53125,-7.5332c0.8779,0.30938 1.77344,0.59961 2.75781,0.59961c4.68248,0 8.5,-3.81752 8.5,-8.5c0,-0.93143 -0.15402,-1.82719 -0.42969,-2.66211l-0.49609,-1.50195l-1.14453,1.09375l-4.66211,4.45313l-2.45703,-0.69336l-0.69336,-2.45703l5.54687,-5.80859l-1.50391,-0.49414c-0.83396,-0.27422 -1.72872,-0.42969 -2.66016,-0.42969zM41.5,2c0.11179,0 0.21006,0.04507 0.32031,0.05078l-3.9375,4.125l1.30664,4.63477l4.63281,1.30664l4.125,-3.93945c0.00579,0.11102 0.05273,0.20972 0.05273,0.32227c0,3.60152 -2.89848,6.5 -6.5,6.5c-0.95719,0 -1.85793,-0.21152 -2.68164,-0.58594l-0.63086,-0.28711l-8.39258,8.39258c-0.64761,0.64761 -1.66879,0.64761 -2.31641,0c-0.64761,-0.64761 -0.64761,-1.66879 0,-2.31641l8.39258,-8.39062l-0.28711,-0.63086c-0.37353,-0.82282 -0.58398,-1.72445 -0.58398,-2.68164c0,-3.60152 2.89848,-6.5 6.5,-6.5zM8,8c-2.206,0 -4,1.794 -4,4v22c0,0.73222 0.21188,1.4091 0.55664,2h-2.55664c-0.55226,0.00006 -0.99994,0.44774 -1,1c0,2.74952 2.25048,5 5,5h38c2.74952,0 5,-2.25048 5,-5c-0.00006,-0.55226 -0.44774,-0.99994 -1,-1h-2.55664c0.34476,-0.5909 0.55664,-1.26778 0.55664,-2v-16.02344c-0.635,0.302 -1.303,0.53994 -2,0.71094v15.3125c0,1.103 -0.897,2 -2,2h-34c-1.103,0 -2,-0.897 -2,-2v-22c0,-1.103 0.897,-2 2,-2h23.11914c-0.07,-0.491 -0.11914,-0.989 -0.11914,-1.5c0,-0.169 0.01739,-0.333 0.02539,-0.5zM3.41211,38h4.58789h34h4.58789c-0.43709,1.11247 -1.31204,2 -2.58789,2h-38c-1.27585,0 -2.1508,-0.88753 -2.58789,-2z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="50px" height="50px" fill-rule="nonzero"><g fill="#000000" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(5.12,5.12)"><path d="M7,4c-2.74609,0 -5,2.25391 -5,5v24c0,2.74609 2.25391,5 5,5h4.09375c0.13672,1.20313 0.12109,2.31641 -0.1875,3.25c-0.37891,1.14844 -1.08594,2.11328 -2.40625,2.90625c-0.37109,0.23438 -0.54297,0.68359 -0.42969,1.10547c0.11328,0.42188 0.49219,0.72266 0.92969,0.73828c4.24219,0 9.10547,-2.21484 11.5625,-8h22.4375c2.74609,0 5,-2.25391 5,-5v-24c0,-2.74609 -2.25391,-5 -5,-5zM7,6h36c1.65625,0 3,1.34375 3,3v24c0,1.65625 -1.34375,3 -3,3h-23c-0.41797,0 -0.79297,0.26172 -0.9375,0.65625c-1.55469,4.24219 -4.33203,6.26172 -7.21875,7c0.39063,-0.55859 0.76172,-1.14844 0.96875,-1.78125c0.51953,-1.57812 0.47656,-3.30469 0.15625,-5.0625c-0.08984,-0.46484 -0.49219,-0.80469 -0.96875,-0.8125h-5c-1.65625,0 -3,-1.34375 -3,-3v-24c0,-1.65625 1.34375,-3 3,-3z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="30px" height="30px"> <path d="M 14.984375 2.4863281 A 1.0001 1.0001 0 0 0 14 3.5 L 14 4 L 8.5 4 A 1.0001 1.0001 0 0 0 7.4863281 5 L 6 5 A 1.0001 1.0001 0 1 0 6 7 L 24 7 A 1.0001 1.0001 0 1 0 24 5 L 22.513672 5 A 1.0001 1.0001 0 0 0 21.5 4 L 16 4 L 16 3.5 A 1.0001 1.0001 0 0 0 14.984375 2.4863281 z M 6 9 L 7.7929688 24.234375 C 7.9109687 25.241375 8.7633438 26 9.7773438 26 L 20.222656 26 C 21.236656 26 22.088031 25.241375 22.207031 24.234375 L 24 9 L 6 9 z"/></svg>

After

Width:  |  Height:  |  Size: 560 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
</svg>

After

Width:  |  Height:  |  Size: 320 B

@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Elasticsearch</title><path fill="#00BFB3" d="M13.394 0C8.683 0 4.609 2.716 2.644 6.667h15.641a4.77 4.77 0 0 0 3.073-1.11c.446-.375.864-.785 1.247-1.243l.001-.002A11.974 11.974 0 0 0 13.394 0zM1.804 8.889a12.009 12.009 0 0 0 0 6.222h14.7a3.111 3.111 0 1 0 0-6.222zm.84 8.444C4.61 21.283 8.684 24 13.395 24c3.701 0 7.011-1.677 9.212-4.312l-.001-.002a9.958 9.958 0 0 0-1.247-1.243 4.77 4.77 0 0 0-3.073-1.11z"/></svg>

After

Width:  |  Height:  |  Size: 493 B

+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
<circle cx="120" cy="120" r="120" fill="#6366F1"/>
<rect x="52" y="78" width="136" height="88" rx="12" fill="#FFF"/>
<path d="M58 88l62 46 62-46" fill="none" stroke="#6366F1" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 318 B

+1
View File
@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Grafana</title><path fill="#F46800" d="M23.02 10.59a8.578 8.578 0 0 0-.862-3.034 8.911 8.911 0 0 0-1.789-2.445c.337-1.342-.413-2.505-.413-2.505-1.292-.08-2.113.4-2.416.62-.052-.02-.102-.044-.154-.064-.22-.089-.446-.172-.677-.247-.231-.073-.47-.14-.711-.197a9.867 9.867 0 0 0-.875-.161C14.557.753 12.94 0 12.94 0c-1.804 1.145-2.147 2.744-2.147 2.744l-.018.093c-.098.029-.2.057-.298.088-.138.042-.275.094-.413.143-.138.055-.275.107-.41.166a8.869 8.869 0 0 0-1.557.87l-.063-.029c-2.497-.955-4.716.195-4.716.195-.203 2.658.996 4.33 1.235 4.636a11.608 11.608 0 0 0-.607 2.635C1.636 12.677.953 15.014.953 15.014c1.926 2.214 4.171 2.351 4.171 2.351.003-.002.006-.002.006-.005.285.509.615.994.986 1.446.156.19.32.371.488.548-.704 2.009.099 3.68.099 3.68 2.144.08 3.553-.937 3.849-1.173a9.784 9.784 0 0 0 3.164.501h.08l.055-.003.107-.002.103-.005.003.002c1.01 1.44 2.788 1.646 2.788 1.646 1.264-1.332 1.337-2.653 1.337-2.94v-.058c0-.02-.003-.039-.003-.06.265-.187.52-.387.758-.6a7.875 7.875 0 0 0 1.415-1.7c1.43.083 2.437-.885 2.437-.885-.236-1.49-1.085-2.216-1.264-2.354l-.018-.013-.016-.013a.217.217 0 0 1-.031-.02c.008-.092.016-.18.02-.27.011-.162.016-.323.016-.48v-.253l-.005-.098-.008-.135a1.891 1.891 0 0 0-.01-.13c-.003-.042-.008-.083-.013-.125l-.016-.124-.018-.122a6.215 6.215 0 0 0-2.032-3.73 6.015 6.015 0 0 0-3.222-1.46 6.292 6.292 0 0 0-.85-.048l-.107.002h-.063l-.044.003-.104.008a4.777 4.777 0 0 0-3.335 1.695c-.332.4-.592.84-.768 1.297a4.594 4.594 0 0 0-.312 1.817l.003.091c.005.055.007.11.013.164a3.615 3.615 0 0 0 .698 1.82 3.53 3.53 0 0 0 1.827 1.282c.33.098.66.14.971.137.039 0 .078 0 .114-.002l.063-.003c.02 0 .041-.003.062-.003.034-.002.065-.007.099-.01.007 0 .018-.003.028-.003l.031-.005.06-.008a1.18 1.18 0 0 0 .112-.02c.036-.008.072-.013.109-.024a2.634 2.634 0 0 0 .914-.415c.028-.02.056-.041.085-.065a.248.248 0 0 0 .039-.35.244.244 0 0 0-.309-.06l-.078.042c-.09.044-.184.083-.283.116a2.476 2.476 0 0 1-.475.096c-.028.003-.054.006-.083.006l-.083.002c-.026 0-.054 0-.08-.002l-.102-.006h-.012l-.024.006c-.016-.003-.031-.003-.044-.006-.031-.002-.06-.007-.091-.01a2.59 2.59 0 0 1-.724-.213 2.557 2.557 0 0 1-.667-.438 2.52 2.52 0 0 1-.805-1.475 2.306 2.306 0 0 1-.029-.444l.006-.122v-.023l.002-.031c.003-.021.003-.04.005-.06a3.163 3.163 0 0 1 1.352-2.29 3.12 3.12 0 0 1 .937-.43 2.946 2.946 0 0 1 .776-.101h.06l.07.002.045.003h.026l.07.005a4.041 4.041 0 0 1 1.635.49 3.94 3.94 0 0 1 1.602 1.662 3.77 3.77 0 0 1 .397 1.414l.005.076.003.075c.002.026.002.05.002.075 0 .024.003.052 0 .07v.065l-.002.073-.008.174a6.195 6.195 0 0 1-.08.639 5.1 5.1 0 0 1-.267.927 5.31 5.31 0 0 1-.624 1.13 5.052 5.052 0 0 1-3.237 2.014 4.82 4.82 0 0 1-.649.066l-.039.003h-.287a6.607 6.607 0 0 1-1.716-.265 6.776 6.776 0 0 1-3.4-2.274 6.75 6.75 0 0 1-.746-1.15 6.616 6.616 0 0 1-.714-2.596l-.005-.083-.002-.02v-.056l-.003-.073v-.096l-.003-.104v-.07l.003-.163c.008-.22.026-.45.054-.678a8.707 8.707 0 0 1 .28-1.355c.128-.444.286-.872.473-1.277a7.04 7.04 0 0 1 1.456-2.1 5.925 5.925 0 0 1 .953-.763c.169-.111.343-.213.524-.306.089-.05.182-.091.273-.135.047-.02.093-.042.138-.062a7.177 7.177 0 0 1 .714-.267l.145-.045c.049-.015.098-.026.148-.041.098-.029.197-.052.296-.076.049-.013.1-.02.15-.033l.15-.032.151-.028.076-.013.075-.01.153-.024c.057-.01.114-.013.171-.023l.169-.021c.036-.003.073-.008.106-.01l.073-.008.036-.003.042-.002c.057-.003.114-.008.171-.01l.086-.006h.023l.037-.003.145-.007a7.999 7.999 0 0 1 1.708.125 7.917 7.917 0 0 1 2.048.68 8.253 8.253 0 0 1 1.672 1.09l.09.077.089.078c.06.052.114.107.171.159.057.052.112.106.166.16.052.055.107.107.159.164a8.671 8.671 0 0 1 1.41 1.978c.012.026.028.052.04.078l.04.078.075.156c.023.051.05.1.07.153l.065.15a8.848 8.848 0 0 1 .45 1.34.19.19 0 0 0 .201.142.186.186 0 0 0 .172-.184c.01-.246.002-.532-.024-.856z"/></svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="50px" height="50px" fill-rule="nonzero"><g fill="#ffffff" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(5.12,5.12)"><path d="M24.99805,6.00195c-0.35375,0 -0.70564,0.12033 -0.99414,0.36133l-16.82422,14.05664c-0.212,0.178 -0.24145,0.49308 -0.06445,0.70508c0.179,0.212 0.49308,0.2405 0.70508,0.0625l2.17969,-1.82031v16.63281c0,1.657 1.343,3 3,3h24c1.657,0 3,-1.343 3,-3v-16.63281l2.17969,1.82227c0.093,0.078 0.20731,0.11523 0.32031,0.11523c0.143,0 0.28577,-0.05973 0.38477,-0.17773c0.177,-0.212 0.14755,-0.52808 -0.06445,-0.70508l-2.82031,-2.35547v-7.06641c0,-0.552 -0.448,-1 -1,-1h-1c-0.552,0 -1,0.448 -1,1v4.55859l-11.00586,-9.19531c-0.2885,-0.241 -0.64234,-0.36133 -0.99609,-0.36133zM24.99805,7.00391c0.12825,0 0.25683,0.04336 0.36133,0.13086l13.64063,11.39844v17.4668c0,1.105 -0.895,2 -2,2h-7v-11.33203c0,-0.921 -0.74602,-1.66797 -1.66602,-1.66797h-6.66602c-0.921,0 -1.66797,0.74697 -1.66797,1.66797v11.33203h-7c-1.105,0 -2,-0.895 -2,-2v-17.46875l13.63867,-11.39648c0.1045,-0.0875 0.23112,-0.13086 0.35938,-0.13086z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path d="M17.2 8.9V6.1a2 2 0 1 0-1.6 0v2.8a5.6 5.6 0 0 0-2.6 1.1L7.6 6.2a2.2 2.2 0 1 0-1.1 1.3l5.2 3.7a5.6 5.6 0 1 0 5.5-2.3Zm-.8 8.9a3.2 3.2 0 1 1 0-6.4 3.2 3.2 0 0 1 0 6.4Z" fill="#FF7A59"/>
<path d="m8.3 15.6-2 1.9a1.8 1.8 0 1 0 1.2 1.2l2-1.9a5.7 5.7 0 0 1-1.2-1.2Z" fill="#FF7A59"/>
</svg>

After

Width:  |  Height:  |  Size: 371 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="30px" height="30px" fill-rule="nonzero"><g fill="#000000" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(8.53333,8.53333)"><path d="M14,2v8.14063c1.722,0.446 3,1.99738 3,3.85938c0,0.631 -0.15892,1.22095 -0.41992,1.75195l2.61523,2.2793c0.098,-0.021 0.19969,-0.03125 0.30469,-0.03125c0.828,0 1.5,0.672 1.5,1.5c0,0.828 -0.672,1.5 -1.5,1.5c-0.775,0 -1.40638,-0.5907 -1.48437,-1.3457l-2.73047,-2.37891c-0.909,0.636 -2.08689,0.91289 -3.33789,0.58789c-1.399,-0.362 -2.51984,-1.51773 -2.83984,-2.92773c-0.514,-2.26 0.88858,-4.27492 2.89258,-4.79492v-7.95117c-5.119,0.942 -9,5.41955 -9,10.81055c0,4.026 2.25142,6.79659 3.73242,8.18359c0.81,0.758 1.26758,1.81092 1.26758,2.91992v3.89648h10v-1.55859c0,-0.861 0.55019,-1.62448 1.36719,-1.89648l2.58203,-0.85937c1.225,-0.41 2.05078,-1.55666 2.05078,-2.84766v-3.09375l2.26758,-0.78125c0.43262,-0.12014 0.73213,-0.5139 0.73242,-0.96289c-0.00069,-0.23009 -0.08071,-0.4529 -0.22656,-0.63086l-0.02734,-0.0332c-0.00952,-0.01128 -0.01929,-0.02235 -0.0293,-0.0332l-2.75586,-3.30469c-0.40743,-5.60491 -4.22408,-9.99805 -9.96094,-9.99805zM13,12c-1.10457,0 -2,0.89543 -2,2c0,1.10457 0.89543,2 2,2c1.10457,0 2,-0.89543 2,-2c0,-1.10457 -0.89543,-2 -2,-2z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.0425 12.2025C17.0139 9.34808 19.4159 7.95105 19.5279 7.88465C18.2523 6.06609 16.2641 5.80104 15.5675 5.77504C13.8824 5.59836 12.2523 6.79092 11.3935 6.79092C10.5151 6.79092 9.18511 5.79705 7.77308 5.82905C5.93451 5.86105 4.22497 6.91695 3.28739 8.54686C1.35364 11.8747 2.79168 16.7843 4.64625 19.5907C5.56783 20.9647 6.64973 22.4968 8.09977 22.4448C9.51779 22.3868 10.0576 21.5344 11.7687 21.5344C13.4602 21.5344 13.9641 22.4448 15.4401 22.4128C16.9642 22.3868 17.9018 21.0127 18.7874 19.6287C19.8433 18.0508 20.2752 16.5027 20.2952 16.4247C20.2552 16.4087 17.0745 15.1522 17.0425 12.2025Z" fill="black"/>
<path d="M14.7278 3.6875C15.4884 2.75592 16.0083 1.47536 15.8643 0.179688C14.7478 0.227687 13.3878 0.931695 12.5992 1.83927C11.8947 2.64766 11.2708 3.96022 11.4388 5.2208C12.6824 5.3208 13.9392 4.60108 14.7278 3.6875Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 999 B

+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.7071 4.29289C14.0976 4.68342 14.0976 5.31658 13.7071 5.70711L7.70711 11.7071C7.31658 12.0976 6.68342 12.0976 6.29289 11.7071L2.29289 7.70711C1.90237 7.31658 1.90237 6.68342 2.29289 6.29289C2.68342 5.90237 3.31658 5.90237 3.70711 6.29289L7 9.58579L12.2929 4.29289C12.6834 3.90237 13.3166 3.90237 13.7071 4.29289Z" fill="#10B981"/>
</svg>

After

Width:  |  Height:  |  Size: 487 B

+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.8055 10.0415H21V10H12V14H17.6515C16.827 16.3285 14.6115 18 12 18C8.6865 18 6 15.3135 6 12C6 8.6865 8.6865 6 12 6C13.5295 6 14.921 6.577 15.9805 7.5195L18.809 4.691C17.023 3.0265 14.634 2 12 2C6.4775 2 2 6.4775 2 12C2 17.5225 6.4775 22 12 22C17.5225 22 22 17.5225 22 12C22 11.3295 21.931 10.675 21.8055 10.0415Z" fill="#FFC107"/>
<path d="M3.15302 7.3455L6.43852 9.755C7.32752 7.554 9.48052 6 12 6C13.5295 6 14.921 6.577 15.9805 7.5195L18.809 4.691C17.023 3.0265 14.634 2 12 2C8.15902 2 4.82802 4.1685 3.15302 7.3455Z" fill="#FF3D00"/>
<path d="M12 22C14.583 22 16.93 21.0115 18.7045 19.404L15.6095 16.785C14.5718 17.5742 13.3038 18.0011 12 18C9.39897 18 7.19047 16.3415 6.35847 14.027L3.09747 16.5395C4.75247 19.778 8.11347 22 12 22Z" fill="#4CAF50"/>
<path d="M21.8055 10.0415H21V10H12V14H17.6515C17.2571 15.1082 16.5467 16.0766 15.608 16.7855L15.6095 16.7845L18.7045 19.4035C18.4855 19.6025 22 17 22 12C22 11.3295 21.931 10.675 21.8055 10.0415Z" fill="#1976D2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+15
View File
@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
<defs>
<radialGradient id="ig-grad" cx="0.3" cy="1.07" r="1.3">
<stop offset="0" stop-color="#fdf497"/>
<stop offset="0.05" stop-color="#fdf497"/>
<stop offset="0.45" stop-color="#fd5949"/>
<stop offset="0.6" stop-color="#d6249f"/>
<stop offset="0.9" stop-color="#285AEB"/>
</radialGradient>
</defs>
<rect x="28" y="28" width="184" height="184" rx="44" fill="url(#ig-grad)"/>
<rect x="66" y="66" width="108" height="108" rx="30" fill="none" stroke="#FFF" stroke-width="12"/>
<circle cx="120" cy="120" r="27" fill="none" stroke="#FFF" stroke-width="12"/>
<circle cx="153" cy="87" r="8.5" fill="#FFF"/>
</svg>

After

Width:  |  Height:  |  Size: 721 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 11H9V5H4V11ZM4 18H9V12H4V18ZM10 18H15V12H10V18ZM16 18H21V12H16V18ZM10 11H15V5H10V11ZM16 5V11H21V5H16Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 242 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><defs><linearGradient id="jira-original-a" gradientUnits="userSpaceOnUse" x1="22.034" y1="9.773" x2="17.118" y2="14.842" gradientTransform="scale(4)"><stop offset=".176" stop-color="#0052cc"/><stop offset="1" stop-color="#2684ff"/></linearGradient><linearGradient id="jira-original-b" gradientUnits="userSpaceOnUse" x1="16.641" y1="15.564" x2="10.957" y2="21.094" gradientTransform="scale(4)"><stop offset=".176" stop-color="#0052cc"/><stop offset="1" stop-color="#2684ff"/></linearGradient></defs><path d="M108.023 16H61.805c0 11.52 9.324 20.848 20.847 20.848h8.5v8.226c0 11.52 9.328 20.848 20.848 20.848V19.977A3.98 3.98 0 00108.023 16zm0 0" fill="#2684ff"/><path d="M85.121 39.04H38.902c0 11.519 9.325 20.847 20.844 20.847h8.504v8.226c0 11.52 9.328 20.848 20.848 20.848V43.016a3.983 3.983 0 00-3.977-3.977zm0 0" fill="url(#jira-original-a)"/><path d="M62.219 62.078H16c0 11.524 9.324 20.848 20.848 20.848h8.5v8.23c0 11.52 9.328 20.844 20.847 20.844V66.059a3.984 3.984 0 00-3.976-3.98zm0 0" fill="url(#jira-original-b)"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#9CA3AF">
<path d="M12 2L2 6v12l10 4 10-4V6L12 2zm0 2.09l7 2.81v8.19l-7 2.81V4.09zM5 15.18V7l5 2v8.18l-5-2z"/>
<path d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>

After

Width:  |  Height:  |  Size: 233 B

+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
<rect x="20" y="20" width="200" height="200" rx="48" fill="#06C755"/>
<path d="M120 62c-38.7 0-70 25.6-70 57.2 0 28.3 25.1 52 59 56.5 2.3.5 5.4 1.5 6.2 3.5.7 1.8.5 4.5.2 6.3l-1 6c-.3 1.8-1.4 7 6.2 3.8s40.9-24.1 55.8-41.2c10.3-11.3 13.6-22.8 13.6-34.9 0-31.6-31.3-57.2-70-57.2z" fill="#FFF"/>
<path d="M92 105v32M78 137h14m24-32v32m14-32v32m0-16l16 16m0-32l-16 16m40-16v32h16" fill="none" stroke="#06C755" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 547 B

+6
View File
@@ -0,0 +1,6 @@
<svg width="32" height="32" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="20" y="20" width="80" height="80" rx="22" fill="#C9F24E"/>
<circle cx="42" cy="60" r="6" fill="#0B0C10"/>
<circle cx="60" cy="60" r="6" fill="#0B0C10"/>
<circle cx="78" cy="60" r="6" fill="#0B0C10"/>
</svg>

After

Width:  |  Height:  |  Size: 322 B

+6
View File
@@ -0,0 +1,6 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="20" y="20" width="80" height="80" rx="22" fill="#C9F24E"/>
<circle cx="42" cy="60" r="6" fill="#0B0C10"/>
<circle cx="60" cy="60" r="6" fill="#0B0C10"/>
<circle cx="78" cy="60" r="6" fill="#0B0C10"/>
</svg>

After

Width:  |  Height:  |  Size: 324 B

+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
<defs>
<radialGradient id="msgr-grad" cx="0.19" cy="0.99" r="1.08">
<stop offset="0" stop-color="#09f"/>
<stop offset="0.6" stop-color="#a033ff"/>
<stop offset="0.93" stop-color="#ff5280"/>
<stop offset="1" stop-color="#ff7061"/>
</radialGradient>
</defs>
<path fill="url(#msgr-grad)" d="M120 28c-52.3 0-92 38.3-92 90 0 27 10.9 50.4 28.7 66.6a7.3 7.3 0 0 1 2.5 5.2l.5 16.3a7.3 7.3 0 0 0 10.3 6.5l18.2-8a7.4 7.4 0 0 1 4.9-.4 100 100 0 0 0 26.9 3.6c52.3 0 92-38.3 92-89.8S172.3 28 120 28z"/>
<path fill="#FFF" d="M64.8 144.3l27-42.9a13.8 13.8 0 0 1 20-3.7l21.5 16.1a5.5 5.5 0 0 0 6.7 0L169 91.8c3.9-2.9 8.9 1.7 6.3 5.8l-27 42.8a13.8 13.8 0 0 1-20 3.7l-21.5-16.1a5.5 5.5 0 0 0-6.7 0l-29 22c-3.9 3-8.9-1.6-6.3-5.7z"/>
</svg>

After

Width:  |  Height:  |  Size: 824 B

+1
View File
@@ -0,0 +1 @@
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 25 0 C 22.800781 0 21 1.800781 21 4 C 21 4.515625 21.101563 5.015625 21.28125 5.46875 C 15.65625 6.929688 12 11.816406 12 18 C 12 25.832031 10.078125 29.398438 8.25 31.40625 C 7.335938 32.410156 6.433594 33.019531 5.65625 33.59375 C 5.265625 33.878906 4.910156 34.164063 4.59375 34.53125 C 4.277344 34.898438 4 35.421875 4 36 C 4 37.375 4.84375 38.542969 6.03125 39.3125 C 7.21875 40.082031 8.777344 40.578125 10.65625 40.96875 C 13.09375 41.472656 16.101563 41.738281 19.40625 41.875 C 19.15625 42.539063 19 43.253906 19 44 C 19 47.300781 21.699219 50 25 50 C 28.300781 50 31 47.300781 31 44 C 31 43.25 30.847656 42.535156 30.59375 41.875 C 33.898438 41.738281 36.90625 41.472656 39.34375 40.96875 C 41.222656 40.578125 42.78125 40.082031 43.96875 39.3125 C 45.15625 38.542969 46 37.375 46 36 C 46 35.421875 45.722656 34.898438 45.40625 34.53125 C 45.089844 34.164063 44.734375 33.878906 44.34375 33.59375 C 43.566406 33.019531 42.664063 32.410156 41.75 31.40625 C 39.921875 29.398438 38 25.832031 38 18 C 38 11.820313 34.335938 6.9375 28.71875 5.46875 C 28.898438 5.015625 29 4.515625 29 4 C 29 1.800781 27.199219 0 25 0 Z M 25 2 C 26.117188 2 27 2.882813 27 4 C 27 5.117188 26.117188 6 25 6 C 23.882813 6 23 5.117188 23 4 C 23 2.882813 23.882813 2 25 2 Z M 27.34375 7.1875 C 32.675781 8.136719 36 12.257813 36 18 C 36 26.167969 38.078125 30.363281 40.25 32.75 C 41.335938 33.941406 42.433594 34.6875 43.15625 35.21875 C 43.515625 35.484375 43.785156 35.707031 43.90625 35.84375 C 44.027344 35.980469 44 35.96875 44 36 C 44 36.625 43.710938 37.082031 42.875 37.625 C 42.039063 38.167969 40.679688 38.671875 38.9375 39.03125 C 35.453125 39.753906 30.492188 40 25 40 C 19.507813 40 14.546875 39.753906 11.0625 39.03125 C 9.320313 38.671875 7.960938 38.167969 7.125 37.625 C 6.289063 37.082031 6 36.625 6 36 C 6 35.96875 5.972656 35.980469 6.09375 35.84375 C 6.214844 35.707031 6.484375 35.484375 6.84375 35.21875 C 7.566406 34.6875 8.664063 33.941406 9.75 32.75 C 11.921875 30.363281 14 26.167969 14 18 C 14 12.261719 17.328125 8.171875 22.65625 7.21875 C 23.320313 7.707031 24.121094 8 25 8 C 25.886719 8 26.679688 7.683594 27.34375 7.1875 Z M 21.5625 41.9375 C 22.683594 41.960938 23.824219 42 25 42 C 26.175781 42 27.316406 41.960938 28.4375 41.9375 C 28.792969 42.539063 29 43.25 29 44 C 29 46.222656 27.222656 48 25 48 C 22.777344 48 21 46.222656 21 44 C 21 43.242188 21.199219 42.539063 21.5625 41.9375 Z"/></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="24px" height="24px" fill-rule="nonzero"><g fill="#000000" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(10.66667,10.66667)"><path d="M4,3c-1.09306,0 -2,0.90694 -2,2v16h14h6v-12c0,-1.09306 -0.90694,-2 -2,-2h-4v-2c0,-1.09306 -0.90694,-2 -2,-2zM4,5h10v14h-10zM6,7v2h2v-2zM10,7v2h2v-2zM16,9h4v10h-4v-2h2v-2h-2v-2h2v-2h-2zM6,11v2h2v-2zM10,11v2h2v-2zM6,15v2h2v-2zM10,15v2h2v-2z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 747 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path d="M13.4 3.5c-1.7 0-2.9.7-3.5 1.5-.1-.5-.4-1.2-1.6-1.2H5.2v3.2h1.3c.2 0 .3.1.3.3v13.2h3.6v-5.2c.6.6 1.7 1.1 3 1.1 3.3 0 5.6-2.6 5.6-6.5 0-3.9-2.2-6.4-5.6-6.4Zm-.7 9.9c-1.5 0-2.4-1.5-2.4-3.4 0-2.6 1.4-3.5 2.5-3.5 1.4 0 2.5 1.4 2.5 3.5 0 2.4-1.3 3.4-2.6 3.4Z" fill="#017737"/>
</svg>

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="48px" height="48px" fill-rule="nonzero"><g fill="#000000" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(4,4)"><path d="M53.41,10.59c0.56,0.56 0.74,1.39 0.46,2.13l-16.23,42c-0.3,0.76 -1.035,1.27 -1.855,1.28c-0.8,0 -1.545,-0.48 -1.855,-1.22l-4.566,-10.848c-0.521,-1.238 -0.386,-2.654 0.359,-3.771l9.007,-13.501c0.6,-0.91 -0.48,-1.99 -1.39,-1.39l-13.501,9.006c-1.117,0.745 -2.534,0.88 -3.771,0.359l-10.848,-4.565c-0.75,-0.32 -1.23,-1.06 -1.22,-1.87c0.01,-0.82 0.52,-1.54 1.28,-1.84l42,-16.23c0.74,-0.28 1.57,-0.1 2.13,0.46z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 896 B

+1
View File
@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Sentry</title><path fill="#8D5CF5" d="M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z"/></svg>

After

Width:  |  Height:  |  Size: 702 B

+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="-18 0 292 292" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M223.774 57.34c-.201-1.46-1.48-2.268-2.537-2.357-1.055-.088-23.383-1.743-23.383-1.743s-15.507-15.395-17.209-17.099c-1.703-1.703-5.029-1.185-6.32-.805-.19.056-3.388 1.043-8.678 2.68-5.18-14.906-14.322-28.604-30.405-28.604-.444 0-.901.018-1.358.044C129.31 3.407 123.644.779 118.75.779c-37.465 0-55.364 46.835-60.976 70.635-14.558 4.511-24.9 7.718-26.221 8.133-8.126 2.549-8.383 2.805-9.45 10.462C21.3 95.806.038 260.235.038 260.235l165.678 31.042 89.77-19.42S223.973 58.8 223.775 57.34zM156.49 40.848l-14.019 4.339c.005-.988.01-1.96.01-3.023 0-9.264-1.286-16.723-3.349-22.636 8.287 1.04 13.806 10.469 17.358 21.32zm-27.638-19.483c2.304 5.773 3.802 14.058 3.802 25.238 0 .572-.005 1.095-.01 1.624-9.117 2.824-19.024 5.89-28.953 8.966 5.575-21.516 16.025-31.908 25.161-35.828zm-11.131-10.537c1.617 0 3.246.549 4.805 1.622-12.007 5.65-24.877 19.88-30.312 48.297l-22.886 7.088C75.694 46.16 90.81 10.828 117.72 10.828z" fill="#95BF46"/><path d="M221.237 54.983c-1.055-.088-23.383-1.743-23.383-1.743s-15.507-15.395-17.209-17.099c-.637-.634-1.496-.959-2.394-1.099l-12.527 256.233 89.762-19.418S223.972 58.8 223.774 57.34c-.201-1.46-1.48-2.268-2.537-2.357" fill="#5E8E3E"/><path d="M135.242 104.585l-11.069 32.926s-9.698-5.176-21.586-5.176c-17.428 0-18.305 10.937-18.305 13.693 0 15.038 39.2 20.8 39.2 56.024 0 27.713-17.577 45.558-41.277 45.558-28.44 0-42.984-17.7-42.984-17.7l7.615-25.16s14.95 12.835 27.565 12.835c8.243 0 11.596-6.49 11.596-11.232 0-19.616-32.16-20.491-32.16-52.724 0-27.129 19.472-53.382 58.778-53.382 15.145 0 22.627 4.338 22.627 4.338" fill="#FFF"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+27
View File
@@ -0,0 +1,27 @@
<svg class="background-illustration" viewBox="0 0 800 600" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Background Elements -->
<path d="M0 0 L800 0 L800 600 L0 600 Z" fill="#FEF2F2"/>
<path d="M600 100 Q 750 150 700 300 T 600 500 L 800 600 L 800 0 L 600 100Z" fill="#FEE2E2" opacity="0.5"/>
<path d="M650 150 Q 800 200 750 350 T 650 550 L 850 650 L 850 50 L 650 150Z" fill="#FECACA" opacity="0.3"/>
<!-- Chat Bubbles -->
<g transform="translate(100, 150) scale(0.8)">
<!-- AI Chat Bubble -->
<rect x="50" y="50" width="300" height="80" rx="20" fill="#f34611" opacity="0.9"/>
<text x="80" y="95" fill="white" font-family="Inter, sans-serif" font-size="16">Hello! How can I assist you today?</text>
<!-- User Chat Bubble -->
<rect x="150" y="150" width="280" height="80" rx="20" fill="white" stroke="#f34611" stroke-width="2"/>
<text x="180" y="195" fill="#374151" font-family="Inter, sans-serif" font-size="16">I need help with my account</text>
<!-- AI Response Bubble -->
<rect x="50" y="250" width="320" height="80" rx="20" fill="#f34611" opacity="0.9"/>
<text x="80" y="295" fill="white" font-family="Inter, sans-serif" font-size="16">I'll guide you through the process</text>
</g>
<!-- Decorative Elements -->
<circle cx="600" cy="200" r="8" fill="#f34611" opacity="0.6"/>
<circle cx="650" cy="250" r="6" fill="#f34611" opacity="0.4"/>
<circle cx="700" cy="180" r="10" fill="#f34611" opacity="0.5"/>
<circle cx="580" cy="300" r="7" fill="#f34611" opacity="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.2 32.4C19.2 34.2 17.7 35.7 15.9 35.7C14.1 35.7 12.6 34.2 12.6 32.4C12.6 30.6 14.1 29.1 15.9 29.1H19.2V32.4Z" fill="#E01E5A"/>
<path d="M20.7 32.4C20.7 30.6 22.2 29.1 24 29.1C25.8 29.1 27.3 30.6 27.3 32.4V40.5C27.3 42.3 25.8 43.8 24 43.8C22.2 43.8 20.7 42.3 20.7 40.5V32.4Z" fill="#E01E5A"/>
<path d="M24 19.2C22.2 19.2 20.7 17.7 20.7 15.9C20.7 14.1 22.2 12.6 24 12.6C25.8 12.6 27.3 14.1 27.3 15.9V19.2H24Z" fill="#36C5F0"/>
<path d="M24 20.7C25.8 20.7 27.3 22.2 27.3 24C27.3 25.8 25.8 27.3 24 27.3H15.9C14.1 27.3 12.6 25.8 12.6 24C12.6 22.2 14.1 20.7 15.9 20.7H24Z" fill="#36C5F0"/>
<path d="M35.7 24C35.7 22.2 37.2 20.7 39 20.7C40.8 20.7 42.3 22.2 42.3 24C42.3 25.8 40.8 27.3 39 27.3H35.7V24Z" fill="#2EB67D"/>
<path d="M34.2 24C34.2 25.8 32.7 27.3 30.9 27.3C29.1 27.3 27.6 25.8 27.6 24V15.9C27.6 14.1 29.1 12.6 30.9 12.6C32.7 12.6 34.2 14.1 34.2 15.9V24Z" fill="#2EB67D"/>
<path d="M30.9 35.7C32.7 35.7 34.2 37.2 34.2 39C34.2 40.8 32.7 42.3 30.9 42.3C29.1 42.3 27.6 40.8 27.6 39V35.7H30.9Z" fill="#ECB22E"/>
<path d="M30.9 34.2C29.1 34.2 27.6 32.7 27.6 30.9C27.6 29.1 29.1 27.6 30.9 27.6H39C40.8 27.6 42.3 29.1 42.3 30.9C42.3 32.7 40.8 34.2 39 34.2H30.9Z" fill="#ECB22E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
<circle cx="120" cy="120" r="120" fill="#F22F46"/>
<path d="M120 58c-38.7 0-70 26.9-70 60 0 18.8 10.1 35.6 26 46.6V196l27.8-15.5c5.2 1 10.7 1.5 16.2 1.5 38.7 0 70-26.9 70-60s-31.3-64-70-64z" fill="#FFF"/>
<circle cx="90" cy="120" r="9" fill="#F22F46"/>
<circle cx="120" cy="120" r="9" fill="#F22F46"/>
<circle cx="150" cy="120" r="9" fill="#F22F46"/>
</svg>

After

Width:  |  Height:  |  Size: 431 B

+54
View File
@@ -0,0 +1,54 @@
@import './design-tokens.css';
/* Reset */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
font-weight: normal;
}
/* Base Styles */
body {
min-height: 100vh;
color: var(--text-color);
background: var(--background-color);
line-height: 1.6;
font-family: var(--font-family);
font-size: var(--text-base);
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Typography */
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.2;
}
h1 { font-size: var(--text-4xl); }
h2 { font-size: var(--text-3xl); }
h3 { font-size: var(--text-2xl); }
h4 { font-size: var(--text-xl); }
h5 { font-size: var(--text-lg); }
h6 { font-size: var(--text-base); }
/* Links */
a {
color: var(--primary-color);
text-decoration: none;
transition: var(--transition-fast);
}
a:hover {
opacity: 0.8;
}
/* Focus Styles */
:focus-visible {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
+381
View File
@@ -0,0 +1,381 @@
/* Buttons */
.btn {
font-family: var(--font-sans);
font-size: var(--text-base);
font-weight: var(--font-weight-semibold);
padding: var(--space-sm) var(--space-lg);
border-radius: var(--radius-md);
transition: background-color var(--transition-fast), box-shadow var(--transition-fast),
transform var(--transition-fast), border-color var(--transition-fast), opacity var(--transition-fast);
cursor: pointer;
border: 1px solid transparent;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-sm);
line-height: 1.4;
background: var(--accent-solid);
color: #0B0C10;
}
.btn:hover {
opacity: 0.88;
}
.btn:active {
transform: translateY(1px);
}
.btn:focus-visible {
outline: none;
box-shadow: var(--ring-focus);
}
.btn:disabled {
opacity: 0.4;
cursor: not-allowed;
transform: none;
}
.btn-primary {
background: var(--accent-solid);
color: #0B0C10;
border-color: transparent;
}
.btn-primary:hover {
opacity: 0.88;
}
.btn-secondary {
background: var(--o06);
color: var(--text);
border: 1px solid var(--o14);
}
.btn-secondary:hover {
background: var(--o10);
border-color: var(--o20);
}
.btn-ghost {
background: transparent;
color: var(--text3);
border: 1px solid var(--o12);
}
.btn-ghost:hover {
background: var(--o06);
color: var(--text);
border-color: var(--o20);
}
.btn-danger {
background: transparent;
color: var(--c-coral);
border: 1px solid rgba(255,138,115,.3);
}
.btn-danger:hover {
background: rgba(255,138,115,.08);
border-color: rgba(255,138,115,.5);
}
/* Loading state */
.btn.loading {
position: relative;
color: transparent !important;
}
.btn.loading::after {
content: '';
position: absolute;
width: 16px;
height: 16px;
top: 50%;
left: 50%;
margin: -8px 0 0 -8px;
border: 2px solid rgba(11,12,16,.4);
border-right-color: #0B0C10;
border-radius: 50%;
animation: button-loading-spinner 0.75s linear infinite;
}
/* Forms */
.form-group {
margin-bottom: var(--space-lg);
width: 100%;
}
.form-label {
display: block;
margin-bottom: 9px;
font-weight: var(--font-weight-medium);
color: var(--text3);
font-size: 13.5px;
}
.form-input {
width: 100%;
padding: 14px 16px;
font-family: var(--font-sans);
font-size: var(--text-base);
background: var(--bg);
border: 1px solid var(--o12);
border-radius: 12px;
color: var(--text);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.form-input::placeholder {
color: var(--faint);
}
.form-input:hover {
border-color: var(--o20);
}
.form-input:focus,
.form-input:focus-visible {
border-color: var(--accent-ink);
box-shadow: var(--ring-focus);
outline: none;
}
.form-input:disabled {
background: var(--o04);
color: var(--muted);
cursor: not-allowed;
}
.form-input.error,
.form-input[aria-invalid='true'] {
border-color: var(--error-color);
}
.form-input.error:focus,
.form-input[aria-invalid='true']:focus {
box-shadow: 0 0 0 3px var(--error-bg);
}
/* Cards */
.card {
background: var(--surface);
border-radius: 18px;
border: 1px solid var(--o08);
padding: var(--space-xl);
width: 100%;
max-width: 480px;
margin: 0 auto;
}
.card-full {
max-width: none;
}
/* Stepper */
.progress-steps {
display: flex;
justify-content: center;
align-items: flex-start;
gap: var(--space-2xl);
margin-bottom: var(--space-2xl);
}
.progress-step {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: var(--space-md);
width: 140px;
}
.step-indicator {
width: 40px;
height: 40px;
border-radius: var(--radius-full);
background: var(--o05);
border: 2px solid var(--o14);
color: var(--muted);
display: flex;
align-items: center;
justify-content: center;
font-weight: var(--font-weight-semibold);
font-family: var(--font-display);
transition: background-color var(--transition-normal), border-color var(--transition-normal),
color var(--transition-normal);
}
.step-indicator.active {
background: rgba(201,242,78,.1);
border-color: var(--accent-ink);
color: var(--accent-ink);
}
.step-indicator.completed {
background: var(--accent-solid);
border-color: var(--accent-ink);
color: #0B0C10;
}
.step-details h3 {
font-size: var(--text-base);
font-family: var(--font-display);
margin-bottom: var(--space-xs);
color: var(--text);
}
.step-details p {
font-size: var(--text-sm);
color: var(--muted);
}
@media (max-width: 768px) {
.progress-steps {
flex-direction: column;
align-items: flex-start;
gap: var(--space-lg);
}
.progress-step {
flex-direction: row;
width: 100%;
text-align: left;
}
.step-details {
flex: 1;
}
}
/* Modal buttons.
Global rather than scoped because BaseModal takes its actions through a
slot, and slot content is styled by the parent — three modals were each
carrying a byte-identical private copy of these rules.
Distinct from .btn above, which is the app's louder primary action: these
are the quieter Cancel/Confirm pair a dialog footer wants. */
.modal-btn {
padding: 9px 16px;
border-radius: var(--radius-btn, 8px);
font-size: 14px;
font-weight: 600;
cursor: pointer;
border: 1px solid var(--border-color);
background: var(--background-soft);
color: inherit;
}
.modal-btn-primary {
background: var(--accent-solid);
color: var(--on-accent-solid);
border-color: transparent;
}
.modal-btn-danger {
background: var(--c-danger, var(--danger));
color: var(--on-accent-solid);
border-color: transparent;
}
.modal-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Native checkboxes/radios follow the design accent instead of the browser
default (blue), so form controls match the theme app-wide. */
input[type="checkbox"],
input[type="radio"] {
accent-color: var(--accent-ink);
}
/* Nav section heading — shared by the desktop sidebar and the mobile More
sheet so the two menus can't drift apart visually. */
.nav-section-heading {
color: var(--faint);
font-family: var(--font-mono);
font-size: 11px;
font-weight: var(--font-weight-medium);
text-transform: uppercase;
letter-spacing: 0.1em;
}
/* ── Responsive table → cards ────────────────────────────────────────────
Desktop keeps its own grid template; below 768px each row becomes a card:
the primary cell and a trailing badge share line 1, and the remaining
values wrap onto line 2 carrying their own labels (the table header is
hidden, so each value has to name itself).
Markup: .rcards-head on the header row, .rcards-row on each row,
.rcards-primary / .rcards-badge on the line-1 cells, and .rcards-meta
wrapping a .rcards-label + .rcards-value pair on every other cell.
The consuming component must scope its desktop grid to
@media (min-width: 769px) — a scoped SFC rule carries a [data-v-*]
attribute and would otherwise outrank these global classes. */
.rcards-label {
display: none;
}
@media (max-width: 768px) {
.rcards-head {
display: none;
}
.rcards-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px 12px;
}
/* Zero-height full-width break: pushes the meta cells to their own line
without assuming how wide the badge renders */
.rcards-row::after {
content: '';
order: 3;
flex: 1 0 100%;
height: 0;
}
.rcards-primary {
flex: 1 1 auto;
min-width: 0;
order: 1;
}
.rcards-badge {
flex: 0 0 auto;
margin-left: auto;
order: 2;
}
.rcards-meta {
order: 4;
flex: 0 1 auto;
min-width: 0;
max-width: 100%;
display: inline-flex;
align-items: baseline;
gap: 4px;
font-size: 12px;
color: var(--muted);
}
.rcards-label {
display: inline;
font-size: 10.5px;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--faint);
}
/* text-overflow needs its own block box — it does nothing on a flex item */
.rcards-value {
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
@@ -0,0 +1,340 @@
/* ReplyPier Design Tokens — dark-first, lime accent */
:root {
/* Surfaces */
--bg: #0B0C10;
--bg2: #0E0F14;
--surface: #13151C;
--topbar: rgba(11,12,16,.8);
/* Accent */
--accent-ink: #C9F24E; /* accent for text / borders / icons (darkens in light mode) */
--accent-solid: #C9F24E; /* accent for FILLED surfaces (stays lime in both themes) */
--on-accent-solid: #0B0C10; /* text/icon on --accent-solid (dark in both themes) */
--c-lime: #C9F24E;
--c-teal: #5FE3D6;
--c-purple: #9D8CFF;
--c-coral: #FF8A73;
/* Contrast inks — text/icon placed on top of a fill */
--on-accent: #0B0C10; /* on lime accent */
--on-light: #0B0C10; /* on any light fill */
--on-dark: #F5F6F8; /* on any dark/coloured fill (theme-invariant) */
/* Elevated surfaces / header gradient */
--bg-elevated: #101119;
--bg-deep: #0A0B0E;
--header-gradient: linear-gradient(180deg, var(--bg-elevated), var(--bg));
--scrim: rgba(6,7,10,.72);
/* Mode pills (agent editor) */
--mode-ai-bg: #3B82F6;
--mode-ai-fg: #FFFFFF;
--mode-wf-bg: #9D8CFF;
--mode-wf-fg: #0B0C10;
--pill-idle-bg: var(--o05);
--pill-idle-fg: var(--muted);
/* Extended status / chart palette */
--c-danger: #FF6B6B;
--c-warn: #E6A23C;
--c-info: #8FB8FF;
--c-positive: #7CE38B;
--c-neutral: #8A93A3;
--c-online: #8FEDE3; /* online label text */
--c-pro: #C5BDFF; /* PRO badge text */
/* Theme-aware tints (derived from base palette, auto-adjust per theme) */
--accent-bg-06: color-mix(in srgb, var(--accent-ink) 6%, transparent);
--accent-bg-08: color-mix(in srgb, var(--accent-ink) 8%, transparent);
--accent-bg-12: color-mix(in srgb, var(--accent-ink) 12%, transparent);
--accent-border: color-mix(in srgb, var(--accent-ink) 30%, transparent);
--teal-bg-10: color-mix(in srgb, var(--c-teal) 10%, transparent);
--teal-bg: color-mix(in srgb, var(--c-teal) 12%, transparent);
--teal-border: color-mix(in srgb, var(--c-teal) 22%, transparent);
--purple-bg: color-mix(in srgb, var(--c-purple) 15%, transparent);
--purple-border: color-mix(in srgb, var(--c-purple) 28%, transparent);
--coral-bg: color-mix(in srgb, var(--c-coral) 12%, transparent);
--coral-border: color-mix(in srgb, var(--c-coral) 30%, transparent);
/* Controls */
--toggle-knob: #FFFFFF;
--toggle-track-off: var(--o12);
--toggle-on-teal: var(--c-teal);
--toggle-on-accent: var(--accent-ink);
/* Inbox message bubbles */
--bubble-customer-bg: var(--o05);
--bubble-customer-fg: var(--text2);
--bubble-ai-bg: var(--teal-bg-10);
--bubble-ai-border: var(--teal-border);
--bubble-ai-fg: var(--text2);
--bubble-agent-bg: var(--accent-ink);
--bubble-agent-fg: var(--on-accent);
/* Brand gradients / avatars */
--grad-purple-teal: linear-gradient(135deg, var(--c-purple), var(--c-teal));
--grad-lime-teal: linear-gradient(135deg, var(--accent-ink), var(--c-teal));
--grad-generate: linear-gradient(135deg, var(--c-purple), #7C6AE6);
/* Text scale */
--text: #F5F6F8;
--text2: #E7EAF0;
--text3: #C7CCD6;
--muted: #9CA3B0;
/* Small text on every surface (down to #1A1C23) must reach WCAG AA 4.5:1. */
--muted2: #868D9C;
--faint: #7F8696;
--code: #D7DBE3;
/* White-opacity tokens — borders, dividers, overlays */
--o03: rgba(255,255,255,.03);
--o04: rgba(255,255,255,.04);
--o05: rgba(255,255,255,.05);
--o06: rgba(255,255,255,.06);
--o07: rgba(255,255,255,.07);
--o08: rgba(255,255,255,.08);
--o10: rgba(255,255,255,.1);
--o12: rgba(255,255,255,.12);
--o14: rgba(255,255,255,.14);
--o16: rgba(255,255,255,.16);
--o20: rgba(255,255,255,.2);
--o25: rgba(255,255,255,.25);
--o30: rgba(255,255,255,.3);
/* Semantic */
--success-color: #0f9d6e;
--error-color: #DC2626;
--error-dark: #b91c1c;
--warning-color: #D97706;
--info-color: #2563EB;
--success-bg: rgba(15,157,110,.1);
--error-bg: rgba(220,38,38,.1);
--warning-bg: rgba(217,119,6,.1);
--info-bg: rgba(37,99,235,.1);
/* Fonts */
--font-family: 'Instrument Sans', system-ui, -apple-system, sans-serif;
--font-sans: 'Instrument Sans', system-ui, -apple-system, sans-serif;
--font-display: 'Space Grotesk', sans-serif;
--font-mono: 'JetBrains Mono', monospace;
/* Font weights */
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* Letter spacing */
--tracking-display: -0.02em;
--tracking-tight: -0.01em;
--tracking-wide: 0.04em;
/* Font sizes */
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 1.875rem;
--text-4xl: 2.25rem;
/* Spacing */
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 1.5rem;
--space-xl: 2rem;
--space-2xl: 3rem;
--space-3xl: 4rem;
/* Border radius */
--radius-sm: 6px;
--radius-md: 8px;
--radius-lg: 16px;
--radius-full: 9999px;
/* Design-spec radii */
--radius-chip: 10px;
--radius-btn: 11px;
--radius-input: 12px;
--radius-card: 18px;
--radius-card-lg: 20px;
--radius-pill: 999px;
/* Transitions */
--transition-fast: 0.18s cubic-bezier(0.4,0,0.2,1);
--transition-normal: 0.25s cubic-bezier(0.4,0,0.2,1);
--transition-slow: 0.4s cubic-bezier(0.4,0,0.2,1);
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0,0,0,.15);
--shadow-md: 0 4px 12px rgba(0,0,0,.2);
--shadow-lg: 0 20px 50px rgba(0,0,0,.5);
/* Focus ring */
--ring-focus: 0 0 0 3px rgba(201,242,78,.15);
/* Icon filters (for SVG img elements on dark bg) */
--icon-filter: brightness(0) invert(1);
--icon-opacity: 0.5;
--icon-active-opacity: 0.9;
/* Mobile app shell */
--bottom-nav-height: 56px;
--safe-top: env(safe-area-inset-top, 0px);
--safe-bottom: env(safe-area-inset-bottom, 0px);
/* Overlay stacking order — every fixed overlay must use one of these */
--z-topbar: 50;
--z-bottom-nav: 900;
--z-drawer: 1000; /* notification drawer, sidebar overlay */
--z-fullscreen-page: 1050; /* mobile chat-info "Details" page */
--z-prompt: 1090; /* push/install prompts */
--z-sheet: 1100; /* More bottom sheet */
--z-support-chat: 1150; /* hosted support chat drawer (read from JS) */
--z-modal: 2000;
/* Breakpoints — keep in sync with useBreakpoint.ts (JS can't read these
at media-query time: --breakpoint-sm = MOBILE, --breakpoint-md = TABLET) */
--breakpoint-xs: 480px;
--breakpoint-sm: 768px;
--breakpoint-md: 1024px;
--breakpoint-lg: 1280px;
--breakpoint-xl: 1366px;
--breakpoint-xxl: 1440px;
--breakpoint-xxxl: 1600px;
/* Legacy aliases (keep for component compatibility) */
--primary-color: var(--accent-ink);
--primary-dark: #b3d943;
--background-color: var(--bg);
--background-base: var(--surface);
--background-soft: var(--bg2);
--background-mute: var(--o08);
--hover-bg: var(--o05);
--border-color: var(--o10);
--border-color-hover: var(--o16);
--text-primary: var(--text);
--text-secondary: var(--text3);
--text-color: var(--text);
--text-heading: var(--text);
--text-muted: var(--muted);
--text-placeholder: var(--faint);
--code-bg: var(--surface);
--primary-soft: rgba(201,242,78,.1);
/* Legacy aliases used by the agent editor (resolve via flipping tokens) */
--accent-color: var(--accent-ink);
--background-alt: var(--bg2);
--background-muted: var(--o05);
--radius-xl: 20px;
--success: var(--success-color);
--success-dark: var(--success-color);
--success-light: color-mix(in srgb, var(--success-color) 14%, transparent);
--success-color-soft: color-mix(in srgb, var(--success-color) 12%, transparent);
--warning: var(--warning-color);
--warning-light: color-mix(in srgb, var(--warning-color) 14%, transparent);
--error-light: var(--coral-bg);
--error-soft: var(--coral-bg);
--error-color-soft: var(--coral-bg);
--primary-color-filter: none;
}
/* Light theme override */
[data-theme="light"] {
--bg: #EEF0F4;
--bg2: #FFFFFF;
--surface: #FFFFFF;
--topbar: rgba(255,255,255,.85);
/* Accent and status inks are used as text on white and on their own pale
tints: darkened until they reach WCAG AA 4.5:1 on both. */
--accent-ink: #44700A;
--c-lime: #44700A;
--c-teal: #097171;
--c-purple: #6454BF;
--c-coral: #AC4829;
--warning-color: #9C5604;
--text: #171A21;
--text2: #222732;
--text3: #3C424E;
--muted: #5C6473;
/* Small text on every surface (down to #EEF0F4) must reach WCAG AA 4.5:1. */
--muted2: #626977;
--faint: #646B79;
--code: #3C424E;
--o03: rgba(17,21,30,.03);
--o04: rgba(17,21,30,.04);
--o05: rgba(17,21,30,.05);
--o06: rgba(17,21,30,.06);
--o07: rgba(17,21,30,.075);
--o08: rgba(17,21,30,.08);
--o10: rgba(17,21,30,.09);
--o12: rgba(17,21,30,.1);
--o14: rgba(17,21,30,.11);
--o16: rgba(17,21,30,.12);
--o20: rgba(17,21,30,.14);
--o25: rgba(17,21,30,.18);
--o30: rgba(17,21,30,.2);
/* Icon filters for light theme — SVGs are already dark, no inversion needed */
--icon-filter: brightness(1);
--icon-opacity: 0.6;
--icon-active-opacity: 0.85;
--shadow-sm: 0 1px 2px rgba(17,21,30,.05);
--shadow-md: 0 4px 12px rgba(17,21,30,.08);
--shadow-lg: 0 20px 50px rgba(17,21,30,.15);
--ring-focus: 0 0 0 3px rgba(76,122,11,.2);
--primary-soft: rgba(76,122,11,.08);
/* Contrast inks — accent is dark-olive in light, so text on it must be white */
--on-accent: #FFFFFF;
--on-light: #0B0C10;
--on-dark: #F5F6F8;
/* Elevated surfaces / scrim (gradients & tints referencing base tokens auto-recompute) */
--bg-elevated: #FFFFFF;
--bg-deep: #F4F6FA;
--scrim: rgba(17,21,30,.4);
/* Mode pills — darker fills need light text in light theme */
--mode-ai-bg: #2563EB;
--mode-ai-fg: #FFFFFF;
--mode-wf-bg: #6D5BD0;
--mode-wf-fg: #FFFFFF;
/* Extended status / chart palette tuned for light bg: each reaches WCAG AA
4.5:1 as text on white and on its own pale tint. */
--c-danger: #B33F3F;
--c-warn: #8F5E18;
--c-info: #225BD8;
--c-positive: #187742;
--c-neutral: #606773;
--c-online: #097171;
--c-pro: #6454BF;
/* Generate gradient — darker end stop for light */
--grad-generate: linear-gradient(135deg, var(--c-purple), #5B49C9);
/* Legacy aliases */
--primary-color: var(--accent-ink);
--primary-dark: #3d6209;
--background-color: var(--bg);
--background-base: var(--surface);
--background-soft: var(--bg2);
--background-mute: var(--o08);
--hover-bg: var(--o05);
--border-color: var(--o10);
--border-color-hover: var(--o16);
--text-primary: var(--text);
--text-secondary: var(--text3);
--text-color: var(--text);
--text-heading: var(--text);
--text-muted: var(--muted);
--text-placeholder: var(--faint);
--code-bg: var(--surface);
}
+9
View File
@@ -0,0 +1,9 @@
@import './design-tokens.css';
@import './base.css';
@import './components.css';
@import './utilities.css';
#app {
min-height: 100vh;
width: 100%;
}
+95
View File
@@ -0,0 +1,95 @@
/* Layout */
.container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 0 var(--space-xl);
min-height: 100vh;
}
.container-fluid {
width: 100%;
margin: 0 auto;
padding: 0 var(--space-xl);
}
/* Responsive containers */
@media (max-width: 1366px) {
.container,
.container-fluid {
max-width: 1200px;
padding: 0 var(--space-lg);
}
}
@media (max-width: 1280px) {
.container,
.container-fluid {
padding: 0 var(--space-md);
}
}
@media (max-width: 768px) {
.container,
.container-fluid {
padding: 0 var(--space-sm);
}
}
/* Flexbox */
.flex { display: flex; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.gap-sm { gap: var(--space-sm); }
.gap-md { gap: var(--space-md); }
.gap-lg { gap: var(--space-lg); }
/* Spacing */
.m-0 { margin: 0; }
.mt-lg { margin-top: var(--space-lg); }
.mb-lg { margin-bottom: var(--space-lg); }
.mx-auto { margin-left: auto; margin-right: auto; }
/* Text */
.text-center { text-align: center; }
.text-sm { font-size: var(--text-sm); }
.text-lg { font-size: var(--text-lg); }
.font-bold { font-weight: 600; }
.opacity-80 { opacity: 0.8; }
/* Responsive visibility utilities */
.hidden { display: none !important; }
.visible { display: block !important; }
/* Show/hide on specific screen sizes */
@media (max-width: 1366px) {
.hidden-laptop-13 { display: none !important; }
.visible-laptop-13 { display: block !important; }
}
@media (max-width: 1280px) {
.hidden-laptop-small { display: none !important; }
.visible-laptop-small { display: block !important; }
}
@media (max-width: 1024px) {
.hidden-tablet { display: none !important; }
.visible-tablet { display: block !important; }
}
@media (max-width: 768px) {
.hidden-mobile { display: none !important; }
.visible-mobile { display: block !important; }
}
/* Responsive spacing utilities for 13-inch laptops */
@media (max-width: 1366px) {
.p-responsive-13 { padding: var(--space-md) !important; }
.px-responsive-13 { padding-left: var(--space-md) !important; padding-right: var(--space-md) !important; }
.py-responsive-13 { padding-top: var(--space-md) !important; padding-bottom: var(--space-md) !important; }
.m-responsive-13 { margin: var(--space-md) !important; }
.mx-responsive-13 { margin-left: var(--space-md) !important; margin-right: var(--space-md) !important; }
.my-responsive-13 { margin-top: var(--space-md) !important; margin-bottom: var(--space-md) !important; }
}
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 4H3C1.89543 4 1 4.89543 1 6V18C1 19.1046 1.89543 20 3 20H21C22.1046 20 23 19.1046 23 18V6C23 4.89543 22.1046 4 21 4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1 10H23" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 15H7.01" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 15H13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 701 B

+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
<defs>
<linearGradient id="tg-grad" x1="0.667" y1="0.167" x2="0.417" y2="0.75">
<stop offset="0" stop-color="#37aee2"/>
<stop offset="1" stop-color="#1e96c8"/>
</linearGradient>
</defs>
<circle cx="120" cy="120" r="120" fill="url(#tg-grad)"/>
<path fill="#c8daea" d="M98 175c-3.888 0-3.227-1.468-4.568-5.17L82 132.207 170 80"/>
<path fill="#a9c9dd" d="M98 175c3 0 4.325-1.372 6-3l16-15.558-19.958-12.035"/>
<path fill="#f6fbfe" d="M100.04 144.41l48.36 35.729c5.519 3.045 9.501 1.468 10.876-5.123l19.685-92.763c2.015-8.08-3.08-11.746-8.36-9.349l-115.59 44.571c-7.89 3.165-7.843 7.567-1.438 9.528l29.663 9.259 68.673-43.325c3.242-1.966 6.218-.91 3.776 1.258"/>
</svg>

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="32px" height="32px" fill-rule="nonzero"><g fill="#000000" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(8,8)"><path d="M16,5c-3.85547,0 -7,3.14453 -7,7c0,2.41016 1.23047,4.55078 3.09375,5.8125c-3.56641,1.53125 -6.09375,5.07031 -6.09375,9.1875h2c0,-2.89062 1.52734,-5.40625 3.8125,-6.8125c0.67188,1.64844 2.30859,2.8125 4.1875,2.8125c1.87891,0 3.51563,-1.16406 4.1875,-2.8125c2.28516,1.40625 3.8125,3.92188 3.8125,6.8125h2c0,-4.11719 -2.52734,-7.65625 -6.09375,-9.1875c1.86328,-1.26172 3.09375,-3.40234 3.09375,-5.8125c0,-3.85547 -3.14453,-7 -7,-7zM16,7c2.77344,0 5,2.22656 5,5c0,2.77344 -2.22656,5 -5,5c-2.77344,0 -5,-2.22656 -5,-5c0,-2.77344 2.22656,-5 5,-5zM16,19c0.82031,0 1.60156,0.11719 2.34375,0.34375c-0.34766,0.96484 -1.25391,1.65625 -2.34375,1.65625c-1.08984,0 -1.99609,-0.69141 -2.34375,-1.65625c0.74219,-0.22656 1.52344,-0.34375 2.34375,-0.34375z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="30px" height="30px">
<path d="M 15 5 C 6.081703 5 0.32098813 14.21118 0.21679688 14.378906 A 1 1 0 0 0 0.21679688 15.621094 C 0.32098813 15.78882 6.081703 25 15 25 C 23.918297 25 29.679012 15.78882 29.783203 15.621094 A 1 1 0 0 0 29.783203 14.378906 C 29.679012 14.21118 23.918297 5 15 5 z M 15 8 C 18.866 8 22 11.134 22 15 C 22 18.866 18.866 22 15 22 C 11.134 22 8 18.866 8 15 C 8 11.134 11.134 8 15 8 z M 15 12 A 3 3 0 0 0 12 15 A 3 3 0 0 0 15 18 A 3 3 0 0 0 18 15 A 3 3 0 0 0 15 12 z"/>
</svg>

After

Width:  |  Height:  |  Size: 589 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
<circle cx="120" cy="120" r="120" fill="#25D366"/>
<path fill="#FFF" d="M164.5 75.2A62.4 62.4 0 0 0 120.1 56.8c-34.6 0-62.8 28.2-62.8 62.8 0 11.1 2.9 21.9 8.4 31.4L56.8 183l32.8-8.6a62.7 62.7 0 0 0 30.4 7.8h.1c34.6 0 62.8-28.2 62.8-62.8a62.4 62.4 0 0 0-18.4-44.2zm-44.4 96.6h-.1a52.1 52.1 0 0 1-26.6-7.3l-1.9-1.1-19.7 5.2 5.3-19.2-1.3-2a52 52 0 0 1-8-27.8c0-28.8 23.4-52.2 52.3-52.2a52 52 0 0 1 52.2 52.3c0 28.8-23.4 52.1-52.2 52.1zm28.7-39.1c-1.6-.8-9.3-4.6-10.7-5.1s-2.5-.8-3.5.8-4 5.1-5 6.2-1.8 1.2-3.4.4a42.8 42.8 0 0 1-12.6-7.8 47.6 47.6 0 0 1-8.7-10.9c-.9-1.6-.1-2.4.7-3.2s1.6-1.8 2.4-2.8a10.8 10.8 0 0 0 1.5-2.6 2.9 2.9 0 0 0-.1-2.7c-.4-.8-3.5-8.5-4.8-11.7s-2.6-2.6-3.5-2.7h-3a5.8 5.8 0 0 0-4.2 2c-1.4 1.5-5.5 5.4-5.5 13.1s5.6 15.2 6.4 16.3 11.1 16.9 26.8 23.7a90.6 90.6 0 0 0 8.9 3.3 21.5 21.5 0 0 0 9.9.6c3-.4 9.3-3.8 10.6-7.5s1.3-6.8.9-7.5-1.4-1.1-3-1.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 942 B

+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="7" height="7" rx="1"/>
<rect x="14" y="3" width="7" height="7" rx="1"/>
<rect x="3" y="14" width="7" height="7" rx="1"/>
<rect x="14" y="14" width="7" height="7" rx="1"/>
<circle cx="6.5" cy="6.5" r="1.5" fill="currentColor" stroke="none"/>
<path d="M16 6h3"/>
<path d="M17.5 4.5v3"/>
</svg>

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

+7
View File
@@ -0,0 +1,7 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24 4C12.954 4 4 12.954 4 24C4 35.046 12.954 44 24 44C35.046 44 44 35.046 44 24C44 12.954 35.046 4 24 4Z" fill="#03363D"/>
<path d="M15.5 14H32.5V22.5L24 14L15.5 22.5V14Z" fill="white"/>
<path d="M15.5 34H32.5V25.5L24 34L15.5 25.5V34Z" fill="white"/>
<path d="M34 15.5V32.5H25.5L34 24L25.5 15.5H34Z" fill="white"/>
<path d="M14 15.5V32.5H22.5L14 24L22.5 15.5H14Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 501 B

@@ -0,0 +1,75 @@
<!--
Copyright 2024-2026 ReplyPier
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script setup lang="ts" name="InstallPrompt">
import { computed } from 'vue'
import { usePWAInstall } from '@/composables/usePWAInstall'
import { isShopifyEmbedded } from '@/pwa/register'
const { canInstall, needsManualInstall, promptInstall } = usePWAInstall()
// Dashed hint card per the design: native prompt where supported, share-sheet
// instructions on iOS, hidden when installed/unsupported/embedded.
const visible = computed(() => !isShopifyEmbedded() && (canInstall.value || needsManualInstall.value))
const handleClick = () => {
if (canInstall.value) promptInstall()
}
</script>
<template>
<button v-if="visible" type="button" class="install-hint" @click="handleClick">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="install-icon" aria-hidden="true"><path d="M12 15V3m0 0L8 7m4-4l4 4"/><path d="M5 13v5a3 3 0 003 3h8a3 3 0 003-3v-5"/></svg>
<span class="install-text">
安装 ReplyPier —
<span v-if="needsManualInstall" class="install-sub">分享 → 添加到主屏幕</span>
<span v-else class="install-sub">像原生应用一样使用</span>
</span>
</button>
</template>
<style scoped>
.install-hint {
display: flex;
align-items: center;
gap: 11px;
width: 100%;
padding: 13px 15px;
border-radius: 14px;
border: 1px dashed var(--o10);
background: var(--o03);
color: var(--text3);
font-family: var(--font-sans);
font-size: 12.5px;
line-height: 1.35;
text-align: left;
cursor: pointer;
transition: background-color var(--transition-fast);
}
.install-hint:hover {
background: var(--o05);
}
.install-icon {
color: var(--c-teal);
flex-shrink: 0;
}
.install-sub {
color: var(--muted);
}
</style>
+70
View File
@@ -0,0 +1,70 @@
/*
Copyright 2024-2026 ReplyPier
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { computed, ref } from 'vue'
interface BeforeInstallPromptEvent extends Event {
prompt(): Promise<void>
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>
}
// Module-level: beforeinstallprompt often fires before any component mounts,
// so the listener must be attached at import time.
const deferredPrompt = ref<BeforeInstallPromptEvent | null>(null)
const installed = ref(false)
if (typeof window !== 'undefined') {
window.addEventListener('beforeinstallprompt', (event) => {
event.preventDefault()
deferredPrompt.value = event as BeforeInstallPromptEvent
})
window.addEventListener('appinstalled', () => {
installed.value = true
deferredPrompt.value = null
})
}
export function usePWAInstall() {
const isStandalone = computed(
() =>
installed.value ||
(typeof window !== 'undefined' &&
(window.matchMedia?.('(display-mode: standalone)').matches ||
(navigator as unknown as { standalone?: boolean }).standalone === true))
)
const isIOS = typeof navigator !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent)
// Native prompt available (Chrome/Edge/Android)
const canInstall = computed(() => !isStandalone.value && !!deferredPrompt.value)
// iOS never fires beforeinstallprompt — install is manual via the share sheet
const needsManualInstall = computed(() => !isStandalone.value && isIOS)
const promptInstall = async (): Promise<boolean> => {
const prompt = deferredPrompt.value
if (!prompt) return false
await prompt.prompt()
const choice = await prompt.userChoice
if (choice.outcome === 'accepted') {
deferredPrompt.value = null
return true
}
return false
}
return { isStandalone, isIOS, canInstall, needsManualInstall, promptInstall }
}
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright 2024-2026 ReplyPier
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* Navigations the service worker must NOT answer with the SPA shell.
*
* Lives outside sw.ts so it can be tested without ServiceWorker globals.
*
* Workbox matches these against `url.pathname + url.search`, **not** the
* pathname alone (see workbox-routing/NavigationRoute `_match`). An anchored
* `$` therefore stops matching the moment a provider appends a query string —
* which is precisely the case that matters here, since OAuth providers always
* redirect back with `?code=…`.
*/
export const NAVIGATION_DENYLIST: RegExp[] = [
/^\/api\//,
/^\/widget/,
/^\/webclient/,
/^\/shopify/,
// Anything carrying a file extension is a real file served from public/,
// never an SPA route — no route in the router has one.
//
// This is what keeps meta-oauth-callback.html working: Meta redirects the
// login popup there, and the page exists only to postMessage the code back
// to its opener. Answer it with index.html and the SPA boots instead, fails
// to route the path, lands on the dashboard, and the connect dies silently.
// Being precached does not save it — a precache lookup does not ignore
// `?code=&state=`, so the request misses the entry and reaches the fallback.
//
// `[^?]*` keeps the extension match on the path, so a dot inside a query
// string (`/settings?q=a.b`) is not mistaken for a filename.
/^[^?]*\.[a-z0-9]+(\?|$)/i,
]
/**
* Whether this navigation should bypass the SPA shell. Mirrors workbox's own
* denylist check, so tests exercise the real matching semantics.
*/
export function isDeniedNavigation(pathnameAndSearch: string): boolean {
return NAVIGATION_DENYLIST.some((pattern) => pattern.test(pathnameAndSearch))
}
+33
View File
@@ -0,0 +1,33 @@
/*
Copyright 2024-2026 ReplyPier
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* The message contract between the service worker (src/sw.ts) and the page
* (src/composables/useNotifications.ts). Both sides import from here so a
* rename can never silently break background toasts or notification taps.
*/
export const SW_MESSAGE = {
/** SW → page: a background push arrived; page shows the in-app toast state */
BACKGROUND_NOTIFICATION: 'BACKGROUND_NOTIFICATION',
/** SW → page: user tapped a notification the SW couldn't navigate() itself */
OPEN_CONVERSATION: 'OPEN_CONVERSATION',
} as const
/** The one place the conversation deep-link URL is spelled. */
export const CONVERSATIONS_PATH = '/conversations'
export const conversationSessionUrl = (sessionId?: string | null): string =>
sessionId ? `${CONVERSATIONS_PATH}?session=${encodeURIComponent(sessionId)}` : '/'
+117
View File
@@ -0,0 +1,117 @@
/*
Copyright 2024-2026 ReplyPier
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { registerSW } from 'virtual:pwa-register'
// Never register inside the Shopify admin iframe or on /shopify/* routes —
// the embedded app has its own lifecycle and must not install an app shell.
export const isShopifyEmbedded = (): boolean => {
try {
return window.top !== window.self || window.location.pathname.startsWith('/shopify')
} catch {
return true
}
}
/**
* How long to wait for the new worker to take control before forcing a reload.
* Activation is normally well under a second; this only covers a worker that
* fails to activate or a browser that withholds controllerchange, so the user
* is never stranded on the old build.
*/
const UPDATE_TAKEOVER_TIMEOUT_MS = 4000
/**
* Offer the new build rather than forcing it: an agent mid-reply should not
* have the page reloaded under them. vue-sonner is imported lazily so the
* registration path stays off the startup critical path.
*/
async function promptForUpdate(reload: (reloadPage?: boolean) => Promise<void>) {
try {
const { toast } = await import('vue-sonner')
const id = toast('ReplyPier 有新版本可用', {
description: '刷新页面即可使用最新版本。',
duration: Number.POSITIVE_INFINITY,
action: {
label: '刷新',
onClick: () => {
// Do NOT reload here. registerSW's updateSW ignores its reloadPage
// argument and only posts SKIP_WAITING, without awaiting activation
// — it resolves within a tick. The page refresh is done by the
// `controlling` listener registerSW arms just before it calls us.
//
// Reloading here raced that handshake and always won: the document
// came back under the OLD worker with the new one still in `waiting`,
// so the next load re-dispatched `waiting` and the prompt returned.
// That is why the toast reappeared on every release and no amount of
// clicking Reload cleared it.
toast.dismiss(id)
void reload()
window.setTimeout(() => window.location.reload(), UPDATE_TAKEOVER_TIMEOUT_MS)
},
},
})
} catch (err) {
console.error('Failed to show update prompt:', err)
}
}
export function setupPWA() {
if (!('serviceWorker' in navigator) || isShopifyEmbedded()) return
// Clients from pre-PWA deployments still hold the Firebase-only worker that
// Firebase's getToken() self-registered; drop it so only one SW owns scope /.
navigator.serviceWorker
.getRegistrations()
.then((registrations) => {
registrations.forEach((registration) => {
const scriptUrl =
registration.active?.scriptURL ||
registration.waiting?.scriptURL ||
registration.installing?.scriptURL ||
''
if (scriptUrl.endsWith('firebase-messaging-sw.js')) {
registration.unregister()
}
})
})
.catch(() => {})
const updateSW = registerSW({
immediate: true,
onNeedRefresh() {
promptForUpdate(updateSW)
},
})
}
/**
* The single app SW registration — passed to Firebase's getToken() so it never
* self-registers a second worker. navigator.serviceWorker.ready never settles
* when no SW gets registered (vite dev, or a failed registration), so race it
* against a timeout instead of hanging callers forever.
*/
// Generous enough for a cold first install on low-end devices; a miss only
// delays token registration to the next visit.
const SW_READY_TIMEOUT_MS = 8000
export async function getSWRegistration(): Promise<ServiceWorkerRegistration | undefined> {
if (!('serviceWorker' in navigator) || isShopifyEmbedded()) return undefined
return Promise.race([
navigator.serviceWorker.ready,
new Promise<undefined>((resolve) => setTimeout(() => resolve(undefined), SW_READY_TIMEOUT_MS)),
])
}
+165
View File
@@ -0,0 +1,165 @@
/*
Copyright 2024-2026 ReplyPier
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/// <reference lib="webworker" />
/**
* Single app service worker: precaching/app-shell (installable PWA) plus
* Firebase Cloud Messaging background push. Replaces the old standalone
* public/firebase-messaging-sw.js — Firebase is bundled (the nginx CSP blocks
* importScripts from the gstatic CDN) and its config is inlined at build time
* (exactly what the generated swenv.js used to provide).
*/
import { precacheAndRoute, cleanupOutdatedCaches, createHandlerBoundToURL } from 'workbox-precaching'
import { NavigationRoute, registerRoute } from 'workbox-routing'
import { NetworkFirst, CacheFirst } from 'workbox-strategies'
import { clientsClaim } from 'workbox-core'
import { initializeApp } from 'firebase/app'
import { getMessaging, onBackgroundMessage } from 'firebase/messaging/sw'
import { SW_MESSAGE, conversationSessionUrl } from './pwa/pushContract'
import { NAVIGATION_DENYLIST } from './pwa/navigationDenylist'
declare let self: ServiceWorkerGlobalScope
// A new worker waits until the page asks it to take over, so an agent is
// never reloaded mid-reply. The page prompts and then posts SKIP_WAITING.
// Calling skipWaiting() unconditionally here activated the new worker while
// the open tab kept running the previous bundle's CSS and JS — the app looked
// stale until someone happened to reload.
self.addEventListener('message', (event) => {
if (event.data?.type === 'SKIP_WAITING') {
self.skipWaiting()
}
})
clientsClaim()
precacheAndRoute(self.__WB_MANIFEST)
cleanupOutdatedCaches()
// SPA navigation fallback. What it must not swallow lives in
// ./pwa/navigationDenylist, which is testable without ServiceWorker globals.
registerRoute(
new NavigationRoute(createHandlerBoundToURL('index.html'), {
denylist: NAVIGATION_DENYLIST,
}),
)
// Runtime config is substituted per-environment at container start, so it must
// never be precached. NetworkFirst (not StaleWhileRevalidate): online clients
// must always boot on the CURRENT environment's config — a stale cached copy
// could keep credentialed traffic pointed at a rotated-away endpoint. The
// cache only serves offline boots.
registerRoute(
({ url }) => url.origin === self.location.origin && url.pathname === '/config.js',
new NetworkFirst({ cacheName: 'runtime-config', networkTimeoutSeconds: 3 }),
)
registerRoute(
({ url }) => url.origin === 'https://fonts.googleapis.com' || url.origin === 'https://fonts.gstatic.com',
new CacheFirst({ cacheName: 'google-fonts' }),
)
// ---- Firebase Cloud Messaging (background push) ----
// Pull in the same runtime config the page uses. Published images are built
// once and configured per deployment at container start, so import.meta.env is
// undefined there — without this, self-hosted installs get no background push.
// config.js assigns to `self`, which exists in worker scope; importScripts is
// synchronous and bypasses the SW's own fetch handling.
try {
self.importScripts('/config.js')
} catch {
// Absent in `vite dev` (config.js is served but not always present) and on
// any deployment that bakes its config at build time — the env fallbacks
// below cover both.
}
const runtimeConfig = (self as unknown as { APP_CONFIG?: Record<string, string> }).APP_CONFIG ?? {}
const firebaseConfig = {
apiKey: runtimeConfig.FIREBASE_API_KEY || import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: runtimeConfig.FIREBASE_AUTH_DOMAIN || import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: runtimeConfig.FIREBASE_PROJECT_ID || import.meta.env.VITE_FIREBASE_PROJECT_ID,
messagingSenderId:
runtimeConfig.FIREBASE_MESSAGING_SENDER_ID || import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: runtimeConfig.FIREBASE_APP_ID || import.meta.env.VITE_FIREBASE_APP_ID,
storageBucket:
runtimeConfig.FIREBASE_STORAGE_BUCKET || import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
measurementId:
runtimeConfig.FIREBASE_MEASUREMENT_ID || import.meta.env.VITE_FIREBASE_MEASUREMENT_ID,
}
if (firebaseConfig.apiKey) {
const messaging = getMessaging(initializeApp(firebaseConfig))
onBackgroundMessage(messaging, (payload) => {
// Messages are data-only (title/body in data): a notification payload
// would make the Firebase SDK auto-display a duplicate copy that also
// swallows clicks. The notification?. fallbacks keep older senders working.
const title = payload.data?.title || payload.notification?.title || 'ReplyPier'
const body = payload.data?.body || payload.notification?.body
const sessionId = payload.data?.session_id
self.registration.showNotification(title, {
body,
// icon = the large icon in the expanded tray, which may be full colour.
// badge = the status-bar icon, which Android builds from the ALPHA CHANNEL
// alone: it throws the colours away and tints the silhouette. Pointing this
// at the colour PWA icon is why it rendered as a grey blob — the whole
// square is opaque, so the silhouette is a square.
icon: '/pwa-192x192.png',
badge: '/notification-badge-96.png',
// One stacked notification per conversation (or per notification otherwise)
tag: sessionId || payload.data?.notification_id || undefined,
data: payload.data,
})
// Let any open windows refresh their in-app notification state
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clients) => {
clients.forEach((client) =>
client.postMessage({ eventType: SW_MESSAGE.BACKGROUND_NOTIFICATION, data: payload.data }),
)
})
})
}
// Deep-link straight into the conversation on tap (default action)
self.addEventListener('notificationclick', (event) => {
event.notification.close()
const url = conversationSessionUrl(event.notification.data?.session_id)
event.waitUntil(
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then(async (clients) => {
// Prefer the focused window so a background tab isn't yanked to the session
const client = clients.find((c) => c.focused) ?? clients[0]
if (client) {
await client.focus()
try {
// navigate() rejects for windows this SW doesn't control
// (hard-reloaded or first-visit tabs) — fall back to messaging the
// page so the in-app router performs the navigation.
await client.navigate(url)
} catch {
client.postMessage({ eventType: SW_MESSAGE.OPEN_CONVERSATION, url })
}
return
}
return self.clients.openWindow(url)
}),
)
})
+126
View File
@@ -0,0 +1,126 @@
/*
Copyright 2024-2026 ReplyPier
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Procedural "aurora" gradient orbs used as an avatar alternative to an uploaded photo.
// Colors are hardcoded hex (not CSS vars) so the orb renders identically in the dashboard,
// the preview panel, and the isolated widget bundle (which has no access to app design tokens).
interface OrbPalette {
stops: string
glow: string
}
// Each entry is a visibly distinct colored orb the user can choose from.
const ORB_PALETTES: OrbPalette[] = [
{ stops: '#9D8CFF, #5FE3D6, #C9F24E', glow: 'rgba(157,140,255,0.45)' }, // aurora (default)
{ stops: '#FF8A73, #9D8CFF, #5FE3D6', glow: 'rgba(255,138,115,0.40)' }, // coral
{ stops: '#5FE3D6, #C9F24E, #9D8CFF', glow: 'rgba(95,227,214,0.40)' }, // teal
{ stops: '#C9F24E, #5FE3D6, #FF8A73', glow: 'rgba(201,242,78,0.35)' }, // lime
{ stops: '#6EA8FF, #9D8CFF, #5FE3D6', glow: 'rgba(110,168,255,0.42)' }, // blue
{ stops: '#FF7AC6, #9D8CFF, #6EA8FF', glow: 'rgba(255,122,198,0.42)' }, // pink
{ stops: '#FF8A73, #FFC857, #FF7AC6', glow: 'rgba(255,200,87,0.40)' }, // sunset
{ stops: '#7C5CFF, #B388FF, #5FE3D6', glow: 'rgba(124,92,255,0.45)' }, // violet
{ stops: '#0EA5A5, #5FE3D6, #C9F24E', glow: 'rgba(14,165,165,0.40)' }, // emerald
{ stops: '#F34611, #FF8A73, #FFC857', glow: 'rgba(243,70,17,0.38)' }, // ember
]
// Number of selectable orb variants (for rendering the picker grid).
export const ORB_PALETTE_COUNT = ORB_PALETTES.length
// Stable palette index from a seed (agent name) so the same agent always gets the same orb
// when no variant has been explicitly chosen.
export const orbIndexForSeed = (seed: string): number =>
(seed || '').split('').reduce((a, c) => a + c.charCodeAt(0), 0) % ORB_PALETTES.length
// Inline style object for a specific orb variant by index.
export const getOrbStyleAt = (index: number): Record<string, string> => {
const p = ORB_PALETTES[((index % ORB_PALETTES.length) + ORB_PALETTES.length) % ORB_PALETTES.length]
return {
background: `
radial-gradient(circle at 32% 28%, rgba(255,255,255,0.22) 0%, transparent 42%),
radial-gradient(circle at 68% 72%, rgba(0,0,0,0.25) 0%, transparent 38%),
radial-gradient(ellipse at 50% 50%, ${p.stops})
`.trim(),
boxShadow: `0 4px 28px ${p.glow}, inset 0 1px 0 rgba(255,255,255,0.15)`,
borderRadius: '50%',
}
}
// Inline style for the orb, keyed by a seed (used as the stable default).
export const getOrbStyle = (seed: string): Record<string, string> => getOrbStyleAt(orbIndexForSeed(seed))
// Resolve the orb style for an agent: an explicitly chosen variant wins, otherwise
// fall back to the seed-derived default. `variant` is whatever is stored in
// customization_metadata.orb_variant (may be undefined / non-numeric).
export const resolveOrbStyle = (seed: string, variant: unknown): Record<string, string> => {
const idx = typeof variant === 'number' && Number.isFinite(variant) ? variant : orbIndexForSeed(seed)
return getOrbStyleAt(idx)
}
// Render an orb variant as a self-contained SVG data URI. Storing this in
// `photo_url` lets every existing avatar render site (agent list, widget header,
// previews) show the orb with no orb-specific logic — they just render the image.
export const orbSvgDataUri = (seed: string, variant?: unknown): string => {
const idx = typeof variant === 'number' && Number.isFinite(variant) ? variant : orbIndexForSeed(seed)
const p = ORB_PALETTES[((idx % ORB_PALETTES.length) + ORB_PALETTES.length) % ORB_PALETTES.length]
const [c1, c2, c3] = p.stops.split(',').map((s) => s.trim())
const svg =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">' +
'<defs>' +
'<radialGradient id="o" cx="50%" cy="50%" r="70%">' +
`<stop offset="0%" stop-color="${c1}"/>` +
`<stop offset="50%" stop-color="${c2}"/>` +
`<stop offset="100%" stop-color="${c3}"/>` +
'</radialGradient>' +
'<radialGradient id="h" cx="32%" cy="28%" r="45%">' +
'<stop offset="0%" stop-color="#ffffff" stop-opacity="0.45"/>' +
'<stop offset="100%" stop-color="#ffffff" stop-opacity="0"/>' +
'</radialGradient>' +
'<radialGradient id="s" cx="68%" cy="72%" r="42%">' +
'<stop offset="0%" stop-color="#000000" stop-opacity="0.3"/>' +
'<stop offset="100%" stop-color="#000000" stop-opacity="0"/>' +
'</radialGradient>' +
'</defs>' +
'<circle cx="50" cy="50" r="50" fill="url(#o)"/>' +
'<circle cx="50" cy="50" r="50" fill="url(#h)"/>' +
'<circle cx="50" cy="50" r="50" fill="url(#s)"/>' +
'</svg>'
return `data:image/svg+xml,${encodeURIComponent(svg)}`
}
// Terminal-style avatar mark: a monospace ">" prompt on a dark, FULL-BLEED tile tinted
// with the accent (matches the Terminal chat theme). Full-bleed so it fully covers the
// avatar container at every render site — circular (header/message) or square — with no
// transparent corners. Stored in `photo_url` exactly like the orb, so every avatar
// render site shows it with no mark-specific logic.
export const TERMINAL_MARK_ACCENT = '#C9F24E'
// Only accept simple, safe colour tokens (hex / rgb[a] / hsl[a] / named) before
// embedding into the SVG, so a stray quote in a stored value can't break out of the
// attribute. Anything else falls back to the Terminal lime.
const SAFE_COLOR = /^(#[0-9a-f]{3,8}|rgba?\([\d.,\s%]+\)|hsla?\([\d.,\s%]+\)|[a-z]+)$/i
export const terminalMarkSvgDataUri = (accent: string = TERMINAL_MARK_ACCENT): string => {
const c = accent && SAFE_COLOR.test(accent.trim()) ? accent.trim() : TERMINAL_MARK_ACCENT
const svg =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">' +
// Full-bleed dark base guarantees coverage in any avatar shape.
'<rect width="100" height="100" fill="#0B0C10"/>' +
`<rect width="100" height="100" fill="${c}" fill-opacity="0.16"/>` +
// Inset accent frame for the terminal look.
`<rect x="14" y="14" width="72" height="72" rx="14" fill="none" stroke="${c}" stroke-opacity="0.6" stroke-width="4"/>` +
`<text x="50" y="52" font-family="'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" font-size="52" font-weight="700" fill="${c}" text-anchor="middle" dominant-baseline="central">&gt;</text>` +
'</svg>'
return `data:image/svg+xml,${encodeURIComponent(svg)}`
}