Refine account connections and provider sign-in UI
This commit is contained in:
@@ -65,6 +65,25 @@ Use the spacing tokens for new components. Prefer `8px`, `12px`, and `20px` gaps
|
||||
portrait or uploading a custom portrait, use a compact secondary tab row.
|
||||
Tabs retain native button semantics plus tablist, tab, and tabpanel roles,
|
||||
textual selected state, keyboard focus, and pressed feedback.
|
||||
- Account Access uses a secondary method navigation for passkeys, verified
|
||||
email, local password fallback, and linked identity providers. Each tab shows
|
||||
a compact configured-method count and routes failed actions back to the
|
||||
relevant panel. External identity connections live inside Access rather than
|
||||
competing with it as a top-level account domain.
|
||||
- Future external providers may appear as non-interactive planned rows so the
|
||||
intended identity architecture is visible before credentials are configured.
|
||||
Placeholders must be visually subdued, explicitly labeled `PLANNED`, and must
|
||||
not imitate enabled buttons. Provider-compliant assets and controls replace
|
||||
these rows only when the corresponding OAuth application is implemented.
|
||||
- Provider unlinking does not assume that a password exists. The unlink control
|
||||
is the provider card’s `LINKED` status badge. Hover and keyboard focus turn it
|
||||
red and replace the label with `UNLINK`; activation replaces the full card
|
||||
surface with an explicit Cancel / Confirm Unlink decision. Confirmation uses
|
||||
the square SHD primary action with the orange action accent, while Cancel
|
||||
remains a dark outlined secondary control. The authenticated
|
||||
session plus this deliberate second action authorizes the request; the shared
|
||||
server-side last-method guard remains authoritative. Future provider cards
|
||||
reuse this pattern rather than adding provider-specific credential prompts.
|
||||
- Authentication pages use a split frame: contextual SHD network information on
|
||||
the left and the actionable secure-access console on the right.
|
||||
- OAuth providers appear as connection nodes with a recognizable provider color;
|
||||
@@ -108,6 +127,18 @@ Use the spacing tokens for new components. Prefer `8px`, `12px`, and `20px` gaps
|
||||
with the explicit label “Continue with Discord.”
|
||||
- Discord account connection controls use the same official symbol and Blurple
|
||||
treatment with the explicit label “Link Discord.”
|
||||
- Sign-in pages group external providers into a compact, equal-weight provider
|
||||
rail. Keep a short visible provider name beneath each mark; accessible names
|
||||
and hover text are supplementary and do not replace required brand wording.
|
||||
- Google uses its standard multicolor G on a white icon field, Apple uses a
|
||||
white Apple mark on black, Discord uses its unmodified white symbol on
|
||||
Blurple, and Steam uses its white linked-circle mark on Steam navy.
|
||||
- Providers that are not configured are disabled, desaturated, and marked
|
||||
`PLANNED`; they must not navigate, submit, or visually imply availability.
|
||||
- When each provider is implemented, replace its placeholder with the
|
||||
provider-rendered control or current pre-approved sign-in artwork where the
|
||||
provider requires it. In particular, use Google Identity Services or its
|
||||
approved icon-mode asset and Steam's published sign-in image.
|
||||
- Preserve the provider's required clearspace, logo proportions, approved color,
|
||||
readable contrast, and text/logo alignment even when the surrounding surface
|
||||
follows the SHD theme.
|
||||
|
||||
+5
-1
@@ -57,15 +57,19 @@
|
||||
.catalogue-page,.guides-page,.profile-page{padding-top:34px}.catalogue-hero{min-height:230px;display:flex;justify-content:space-between;align-items:center;padding:25px 32px;margin-bottom:12px;border:1px solid var(--shd-border);background:radial-gradient(circle at 80% 50%,#332714 0,transparent 30%),linear-gradient(120deg,#141718,#0d1011);clip-path:polygon(0 0,97% 0,100% 18%,100% 100%,3% 100%,0 82%)}.catalogue-hero h1{font-size:clamp(48px,6vw,82px);line-height:.82;letter-spacing:-3px;margin:14px 0}.catalogue-hero h1 em{font-style:normal;color:var(--shd-orange)}.catalogue-hero>div>p:last-child{max-width:580px;color:var(--shd-text-muted);font:15px/1.55 Georgia,serif}.catalogue-count{min-width:210px;display:grid;text-align:right}.catalogue-count strong{font-size:38px}.catalogue-count span{font-size:7px;color:#6d7476;letter-spacing:1.5px;margin:3px 0 18px}.catalogue-count .primary{justify-self:end}.filter-bar{display:grid;grid-template-columns:2fr 1fr 1fr auto;gap:7px;align-items:end;margin:12px 0 18px;padding:11px;background:#101314;border:1px solid #2d3233}.filter-bar label{font-size:7px;color:#72797b;letter-spacing:1.2px}.filter-bar input,.filter-bar select{height:38px;margin-top:6px;padding:0 10px;font-size:9px}.filter-bar button{height:38px;padding:0 14px}.catalogue-grid{max-width:none}.catalogue-grid article{position:relative}.build-open{width:100%;margin-top:15px;padding:10px;border:1px solid #3a4041;background:#101314;color:#f6a821;font-size:8px;font-weight:bold;letter-spacing:1px}.guide-layout{display:grid;grid-template-columns:1fr 330px;gap:12px}.guide-feature{min-height:360px;padding:35px;position:relative;border:1px solid #303536;border-top:2px solid #f6a821;background:linear-gradient(135deg,#151819,#0d1011)}.guide-code{position:absolute;right:25px;top:22px;color:#4f5658;font-size:8px;letter-spacing:1.5px}.guide-feature h2{font-size:42px;line-height:.95;margin:20px 0}.guide-feature>p:not(.eyebrow){max-width:650px;color:#858b8d;font:15px/1.6 Georgia,serif}.guide-meta{display:flex;gap:8px;position:absolute;left:35px;bottom:30px}.guide-meta span{display:grid;padding:8px 11px;border:1px solid #303536;color:#60686a;font-size:7px;letter-spacing:1px}.guide-meta b{color:#d7d9d5;font-size:9px;margin-top:4px}.guide-index{border:1px solid #303536;background:#111415;padding:20px}.guide-index>div{display:grid;grid-template-columns:30px 1fr auto;align-items:center;min-height:54px;border-top:1px solid #292e2f}.guide-index small{color:#535b5d;font-size:8px}.guide-index span{font-size:9px;letter-spacing:.8px}.guide-index b{color:#f6a821}
|
||||
.profile-command{display:flex;justify-content:space-between;align-items:center;padding:0 0 24px;border-bottom:1px solid #303536}.profile-ident{display:flex;align-items:center;gap:18px}.profile-avatar{width:76px;height:76px;display:block;object-fit:cover;border:1px solid #f6a821;border-radius:50%;background:#0a0c0d;box-shadow:0 0 0 6px #0a0c0d,0 0 0 7px #f6a82155}.profile-ident h1{font-size:39px;margin:4px 0}.role-badge{display:inline-block;padding:4px 8px;border:1px solid #6e5226;color:#f6a821;font-size:7px;letter-spacing:1.3px}.profile-metrics{display:flex;gap:7px}.profile-metrics div{min-width:77px;height:59px;display:grid;place-items:center;padding:7px;background:#111415;border:1px solid #303536}.profile-metrics strong{font-size:20px}.profile-metrics span{font-size:7px;color:#697174;letter-spacing:1px}.account-alert{display:flex;gap:16px;margin:12px 0;padding:12px 15px;background:#2a2112;border:1px solid #795c2a;border-left:3px solid #f6a821;font-size:10px}.account-alert b{color:#f6a821}.account-alert span{color:#b8ad99}.profile-dashboard{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:12px;margin-top:12px}.profile-feed{display:grid;gap:12px}.profile-module,.account-console{background:#111415;border:1px solid #303536;padding:16px}.module-head{display:flex;justify-content:space-between;align-items:center}.module-head h2,.profile-module h2,.account-console h2{font-size:17px;margin:4px 0 12px}.module-head .secondary{padding:10px 12px}.profile-table{border:1px solid #292e2f}.profile-row{display:grid;grid-template-columns:1.5fr .7fr .7fr .3fr;align-items:center;gap:9px;min-height:47px;padding:7px 11px;border-top:1px solid #292e2f;font-size:9px}.profile-row.heading{min-height:30px;border:0;background:#0d1011;color:#636b6d;font-size:7px;letter-spacing:1px}.profile-row>div{display:grid}.profile-row small{font-size:7px;color:#596164}.profile-row>span{text-transform:uppercase;color:#858d8f}.profile-row button{border:0;background:transparent;color:#f6a821;font-size:8px}.profile-empty{text-align:center;padding:38px}.profile-empty>span{font-size:31px;color:#f6a821}.profile-empty b{display:block;font-size:10px;letter-spacing:1px;margin:10px}.profile-empty p{color:#71797b;font:13px Georgia,serif}.profile-empty a{color:#f6a821;font-size:8px;font-weight:bold}.profile-columns{display:grid;grid-template-columns:1fr 1fr;gap:12px}.large-count{font-size:32px}.count-label{display:block;font-size:7px;color:#687073;letter-spacing:1.2px}.account-console{height:max-content}.console-head{display:flex;justify-content:space-between;padding-bottom:11px;margin-bottom:15px;border-bottom:1px solid #303536;color:#747c7e;font-size:7px;letter-spacing:1.3px}.console-head i{width:6px;height:6px;border-radius:50%;background:#75c08c;box-shadow:0 0 6px #75c08c}.console-action{width:100%;margin-top:9px}.connection-card{display:grid;grid-template-columns:35px 1fr auto;align-items:center;padding:10px;border:1px solid #303536}.discord-node{color:#8993ff;font-size:21px}.connection-card div{display:grid}.connection-card b{font-size:9px}.connection-card small{font-size:7px;color:#656d6f;margin-top:3px}.connection-card a{color:#f6a821;font-size:8px;font-weight:bold}
|
||||
.avatar-picker{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;margin-bottom:10px}.avatar-preset-form fieldset{height:100%}.avatar-preset{display:grid;gap:5px;width:100%;height:100%;padding:4px;background:#0b0e0f;border:1px solid #303536;color:#747c7e}.avatar-preset:hover,.avatar-preset:focus-visible{border-color:#8a672b;color:#f6a821}.avatar-preset.selected{border-color:#f6a821;color:#f6a821;box-shadow:inset 0 -2px #f6a821}.avatar-preset img{display:block;width:100%;aspect-ratio:1;object-fit:cover}.avatar-preset span{font-size:6px;line-height:1.25;text-transform:uppercase;letter-spacing:.4px}.avatar-upload{margin-top:8px}
|
||||
.account-tabs{width:100%;height:auto;margin:0 0 16px;display:grid;grid-template-columns:repeat(4,1fr);border:1px solid var(--shd-border)}.account-tabs button,.account-subtabs button{min-width:0;height:34px;padding:0 5px;border:0;border-left:1px solid var(--shd-border);border-bottom:2px solid transparent;background:#0d1011;color:#6f7779;font-size:7px;font-weight:900;letter-spacing:.8px;cursor:pointer;transition:var(--shd-transition)}.account-tabs button:first-child,.account-subtabs button:first-child{border-left:0}.account-tabs button:hover,.account-tabs button:focus-visible,.account-subtabs button:hover,.account-subtabs button:focus-visible{color:var(--shd-text);background:#171b1c}.account-tabs button.active,.account-subtabs button.active{border-bottom-color:var(--shd-orange);background:#211a0e;color:var(--shd-orange)}.account-tabs button:active,.account-subtabs button:active{transform:translateY(1px);filter:brightness(.85)}.account-tabs button:focus-visible,.account-subtabs button:focus-visible{position:relative;z-index:1;outline:2px solid var(--shd-orange);outline-offset:2px}.account-panel{min-height:290px}.account-subtabs{display:grid;grid-template-columns:1fr 1fr;margin:0 0 10px;border:1px solid var(--shd-border)}.account-subtabs button{height:31px}.account-retention{color:var(--shd-text-muted);font:12px/1.55 Georgia,serif}.danger-eyebrow{color:var(--shd-danger)}
|
||||
.account-tabs{width:100%;height:auto;margin:0 0 16px;display:grid;grid-template-columns:repeat(3,1fr);border:1px solid var(--shd-border)}.account-tabs button,.account-subtabs button{min-width:0;height:34px;padding:0 5px;border:0;border-left:1px solid var(--shd-border);border-bottom:2px solid transparent;background:#0d1011;color:#6f7779;font-size:7px;font-weight:900;letter-spacing:.8px;cursor:pointer;transition:var(--shd-transition)}.account-tabs button:first-child,.account-subtabs button:first-child{border-left:0}.account-tabs button:hover,.account-tabs button:focus-visible,.account-subtabs button:hover,.account-subtabs button:focus-visible{color:var(--shd-text);background:#171b1c}.account-tabs button.active,.account-subtabs button.active{border-bottom-color:var(--shd-orange);background:#211a0e;color:var(--shd-orange)}.account-tabs button:active,.account-subtabs button:active{transform:translateY(1px);filter:brightness(.85)}.account-tabs button:focus-visible,.account-subtabs button:focus-visible{position:relative;z-index:1;outline:2px solid var(--shd-orange);outline-offset:2px}.account-panel{min-height:290px}.account-subtabs{display:grid;grid-template-columns:1fr 1fr;margin:0 0 10px;border:1px solid var(--shd-border)}.account-subtabs button{height:31px}.access-subtabs{display:grid;grid-template-columns:repeat(4,1fr);margin:-2px 0 17px;border:1px solid var(--shd-border);background:#0b0e0f}.access-subtabs button{min-width:0;height:42px;display:grid;place-content:center;gap:3px;padding:4px;border:0;border-left:1px solid var(--shd-border);border-bottom:2px solid transparent;background:transparent;color:var(--shd-text-dim);cursor:pointer;transition:var(--shd-transition)}.access-subtabs button:first-child{border-left:0}.access-subtabs button span{font-size:6px;font-weight:900;letter-spacing:.7px}.access-subtabs button b{font-size:9px}.access-subtabs button:hover,.access-subtabs button:focus-visible{background:#171b1c;color:var(--shd-text)}.access-subtabs button.active{border-bottom-color:var(--shd-orange);background:#211a0e;color:var(--shd-orange)}.access-subtabs button:active{transform:translateY(1px);filter:brightness(.85)}.access-subtabs button:focus-visible{position:relative;z-index:1;outline:2px solid var(--shd-orange);outline-offset:2px}.account-retention{color:var(--shd-text-muted);font:12px/1.55 Georgia,serif}.danger-eyebrow{color:var(--shd-danger)}
|
||||
.account-alert.error-alert{background:#261513;border-color:#75443b;border-left-color:#df8069}.account-alert.error-alert b{color:#df8069}.account-alert.error-alert span{color:#d2aaa1}
|
||||
.danger-zone{margin-top:12px;border:1px solid #563734;background:#181112}.danger-zone summary{padding:10px;color:#df8069;cursor:pointer;font-size:8px;font-weight:bold;letter-spacing:1px}.danger-zone>p,.danger-zone>form{margin:0;padding:0 10px 10px}.danger-zone>p{color:#9f8580;font-size:8px;line-height:1.5}.danger-zone form{padding-bottom:10px}
|
||||
.auth-page{display:block;padding:55px clamp(20px,5vw,80px)}.auth-frame{width:min(980px,100%);min-height:520px;margin:auto;display:grid;grid-template-columns:1.15fr .85fr;border:1px solid #34393a;background:#101314;clip-path:polygon(0 0,96% 0,100% 7%,100% 100%,4% 100%,0 93%)}.auth-intel{width:auto!important;padding:65px!important;border:0!important;border-right:1px solid #303536!important;background:radial-gradient(circle at 15% 80%,#392b15 0,transparent 33%),#0e1112!important}.auth-intel h1{font-size:66px!important;line-height:.85;letter-spacing:-3px;margin:24px 0}.auth-intel h1 em{font-style:normal;color:#f6a821}.auth-intel>p:not(.eyebrow){max-width:470px;color:#909698;font:15px/1.6 Georgia,serif}.auth-status{display:flex;justify-content:space-between;margin-top:55px;padding-top:14px;border-top:1px solid #303536;color:#626a6c;font-size:7px;letter-spacing:1.2px}.auth-status i{display:inline-block;width:5px;height:5px;border-radius:50%;background:#75c08c;box-shadow:0 0 6px #75c08c;margin-right:5px}.auth-console{width:auto!important;padding:34px!important;border:0!important;background:#141718!important}.auth-console .console-head b{color:#f6a821}.auth-console .discord{min-height:48px;margin-top:18px;padding:10px 18px;display:flex;align-items:center;justify-content:center;gap:14px;background:#5865f2;color:#fff;border:0;border-radius:4px;font-size:15px;font-weight:600;letter-spacing:0;text-transform:none}.auth-console .discord img{display:block;width:27px;height:21px;object-fit:contain;flex:0 0 auto}.auth-console .discord:hover{background:#4752c4;filter:none}.auth-console .discord:active{background:#3c45a5;filter:none}.auth-console .discord:focus-visible{outline-color:#e0e3ff}.auth-console small{margin-top:18px}
|
||||
.signin-provider-rail{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:7px}.signin-provider{position:relative;min-width:0;min-height:76px;margin:0;padding:8px 4px 7px;display:grid;grid-template-rows:34px auto;place-items:center;gap:5px;border:1px solid #424849;border-radius:0;background:#101314;color:#dfe1e2;font-family:Arial,Helvetica,sans-serif;font-size:8px;font-style:normal;font-weight:800;letter-spacing:.3px;text-transform:none;cursor:pointer;transition:var(--shd-transition)}.signin-provider-icon{width:34px;height:34px;display:grid;place-items:center}.signin-provider-icon img{display:block;width:21px;height:21px;object-fit:contain}.signin-provider em{position:absolute;right:3px;top:3px;color:#737a7c;font-size:5px;font-style:normal;font-weight:900;letter-spacing:.5px;text-transform:uppercase}.signin-provider:hover:not(:disabled){transform:translateY(-1px);filter:brightness(1.12)}.signin-provider:active:not(:disabled){transform:translateY(2px) scale(.97)}.signin-provider:focus-visible{outline:2px solid var(--shd-orange);outline-offset:2px}.signin-provider:disabled{cursor:not-allowed;opacity:.46;filter:saturate(.55)}.discord-provider{border-color:#5865f2;background:#5865f2;color:#fff}.discord-provider:hover{background:#4752c4;filter:none!important}.discord-provider .signin-provider-icon img{width:25px;height:19px}.google-signin-provider{border-color:#8e918f;background:#131314;color:#e3e3e3}.google-signin-provider .signin-provider-icon{background:#fff}.apple-signin-provider{border-color:#73777a;background:#000;color:#fff}.apple-signin-provider .signin-provider-icon img{width:22px;height:22px}.steam-signin-provider{border-color:#66c0f4;background:#171d25;color:#fff}.steam-signin-provider .signin-provider-icon{color:#fff}.steam-signin-provider .signin-provider-icon img{width:25px;height:25px}
|
||||
.passkey-signin{position:relative;min-height:48px;margin-top:18px}.passkey-signin[aria-busy=true]{padding-right:42px}.passkey-signin[aria-busy=true]:after{content:"";position:absolute;right:16px;width:12px;height:12px;border:2px solid #111;border-right-color:transparent;border-radius:50%;animation:interaction-spin .65s linear infinite}.auth-note{margin:0;color:var(--shd-text-muted);font-size:8px}.password-fallback{margin:18px 0;border:1px solid var(--shd-border);background:#101314}.password-fallback summary{padding:12px;color:#8e9698;font-size:8px;font-weight:900;letter-spacing:1px;cursor:pointer}.password-fallback summary:hover{color:var(--shd-orange)}.password-fallback form{padding:0 12px 12px}.password-fallback .full{margin-bottom:0}
|
||||
.builder-page{padding-top:28px}.builder-command{margin-bottom:14px;border-bottom:1px solid #303536}.builder-command .page-title{margin-bottom:12px}.builder-status{display:flex;gap:24px;padding:9px 2px;color:#61696b;font-size:7px;letter-spacing:1.2px}.builder-status i{display:inline-block;width:5px;height:5px;border-radius:50%;background:#75c08c;box-shadow:0 0 6px #75c08c;margin-right:5px}.builder-page .slot-card{position:relative;overflow:hidden}.builder-page .slot-card:after{content:"";position:absolute;right:-24px;bottom:-24px;width:52px;height:52px;border:1px solid #f6a82122;transform:rotate(45deg)}.builder-page .summary{border-top:2px solid #f6a821}
|
||||
@media(max-width:900px){.filter-bar{grid-template-columns:1fr 1fr}.catalogue-hero{align-items:flex-start}.guide-layout,.profile-dashboard,.auth-frame{grid-template-columns:1fr}.profile-command{align-items:flex-start}.profile-metrics{display:grid;grid-template-columns:1fr 1fr}.account-console{order:-1}.auth-intel{border-right:0!important;border-bottom:1px solid #303536!important;padding:42px!important}.auth-intel h1{font-size:52px!important}}@media(max-width:560px){.catalogue-hero,.profile-command{flex-direction:column;align-items:flex-start}.catalogue-count{text-align:left;margin-top:20px}.catalogue-count .primary{justify-self:start}.filter-bar{grid-template-columns:1fr}.guide-meta{position:static;margin-top:25px;flex-wrap:wrap}.profile-metrics{width:100%;margin-top:20px}.profile-columns{grid-template-columns:1fr}.auth-page{padding:25px 14px}.auth-intel,.auth-console{padding:28px!important}}
|
||||
@media(max-width:420px){.signin-provider-rail{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
|
||||
.discord-connection{display:grid;grid-template-columns:1fr;gap:9px;padding:10px}.connection-copy{display:flex!important;align-items:center;gap:10px}.connection-copy>img{width:28px;height:21px;padding:4px;background:#5865f2;border-radius:3px}.discord-link-button{min-height:42px;padding:8px 14px;display:flex;align-items:center;justify-content:center;gap:12px;background:#5865f2;color:#fff!important;border-radius:4px;font-size:14px!important;font-weight:600!important;letter-spacing:0;text-transform:none}.discord-link-button img{display:block;width:25px;height:19px;object-fit:contain}.discord-link-button:hover{background:#4752c4;filter:none}.discord-link-button:active{background:#3c45a5;filter:none}.discord-link-button:focus-visible{outline:2px solid #e0e3ff;outline-offset:3px}.discord-link-button[aria-busy="true"]::after{content:"";width:11px;height:11px;border:2px solid #fff;border-right-color:transparent;border-radius:50%;animation:interaction-spin .65s linear infinite}
|
||||
.provider-stack{display:grid;gap:7px}.provider-placeholder{min-height:52px;display:grid;grid-template-columns:34px 1fr auto;align-items:center;gap:9px;padding:8px 10px;border:1px solid var(--shd-border-soft);background:#0d1011;color:#6d7476;opacity:.72}.provider-placeholder>div{display:grid;gap:3px}.provider-placeholder b{font-size:9px;color:#8a9092}.provider-placeholder small{font-size:6px;letter-spacing:.7px}.provider-placeholder em{font-style:normal;font-size:6px;font-weight:900;letter-spacing:1px;color:#626a6c}.provider-monogram{width:28px;height:28px;display:grid;place-items:center;border:1px solid #3a4041;background:#151819;font-size:11px;font-weight:900}.google-provider{color:#8ab4f8}.apple-provider{color:#d7d9d5}.steam-provider{color:#70b9d6}
|
||||
.provider-connection-card{position:relative;grid-template-columns:1fr auto!important;min-height:64px;overflow:hidden}.connection-state.provider-unlink-trigger{height:auto;padding:5px 7px;background:transparent;font-family:inherit;cursor:pointer;transition:var(--shd-transition)}.connection-state.provider-unlink-trigger span:last-child{display:none}.connection-state.provider-unlink-trigger:hover:not(:disabled),.connection-state.provider-unlink-trigger:focus-visible{border-color:var(--shd-danger);background:#281716;color:var(--shd-danger)}.connection-state.provider-unlink-trigger:hover:not(:disabled) span:first-child,.connection-state.provider-unlink-trigger:focus-visible span:first-child{display:none}.connection-state.provider-unlink-trigger:hover:not(:disabled) span:last-child,.connection-state.provider-unlink-trigger:focus-visible span:last-child{display:inline}.connection-state.provider-unlink-trigger:focus-visible{outline:2px solid var(--shd-danger);outline-offset:2px}.connection-state.provider-unlink-trigger:disabled{cursor:not-allowed;opacity:.55}.provider-unlink-confirm{position:absolute;z-index:2;inset:0;display:grid!important;grid-template-columns:1fr auto;align-items:center;gap:9px;padding:9px 10px;border:1px solid #9a5043;background:#211414}.provider-unlink-confirm>div:first-child{display:grid!important;gap:3px}.provider-unlink-confirm b{color:var(--shd-danger);font-size:8px;letter-spacing:.8px}.provider-unlink-confirm p{margin:0;color:#d6aaa1;font-size:7px;line-height:1.35}.provider-unlink-confirm>div:last-child{display:grid!important;grid-template-columns:74px 104px;gap:6px}.provider-unlink-confirm form,.provider-unlink-confirm fieldset{height:100%}.provider-unlink-confirm button{width:100%;height:32px;padding:0 7px;border-radius:0;appearance:none;font-size:6px}.provider-unlink-confirm .secondary{border:1px solid #565d5f;background:#101314;color:#d7d9d5}.provider-unlink-confirm .secondary:hover{border-color:#8a672b;color:var(--shd-orange)}.provider-unlink-confirm .primary{border:0;background:var(--shd-orange);color:#111}.provider-unlink-confirm .primary:hover{background:var(--shd-orange-hover)}.provider-card-message{grid-column:1/-1;margin:0}
|
||||
.linked-connection{grid-template-columns:1fr auto}.connection-state{padding:5px 7px;border:1px solid #3f6a4c;color:#75c08c;font-size:7px;font-weight:bold;letter-spacing:1px}.unlink-console{margin-top:8px}.connection-warning{margin:8px 0;color:#c29a60;font-size:8px;line-height:1.4}
|
||||
.isac-identity{margin:10px 0 14px}.isac-identity>.label{margin-bottom:6px}.identity-empty{padding:12px!important;border:1px dashed #343a3b}
|
||||
.isac-avatar-review{margin:10px 0 14px}.isac-avatar-review>.label{margin-bottom:6px}.avatar-review-card{display:grid;grid-template-columns:58px 1fr auto;align-items:center;gap:10px;padding:8px;border:1px solid #303536;background:#0d1011}.avatar-review-card img{width:58px;height:58px;object-fit:cover;border:1px solid #5a4829}.avatar-review-card>div{display:grid;gap:4px}.avatar-review-card b{font-size:9px}.avatar-review-card small{color:#747c7e;font-size:7px;letter-spacing:.4px}.avatar-review-card>span{color:#75c08c;font-size:7px;letter-spacing:1px}.avatar-review-card>span.custom-avatar{color:#f6a821}.avatar-review-note{padding:9px 0 0}
|
||||
|
||||
@@ -234,19 +234,15 @@ export const actions: Actions = {
|
||||
cookies.delete('shd_session', { path: '/' });
|
||||
redirect(303, '/?account_deleted=1');
|
||||
},
|
||||
unlinkDiscord: async ({ request, cookies }) => {
|
||||
unlinkDiscord: async ({ cookies }) => {
|
||||
const user = await getSessionUser(cookies);
|
||||
if (!user) redirect(303, '/sign-in');
|
||||
const password = String((await request.formData()).get('password') ?? '');
|
||||
const database = await getDatabase();
|
||||
const credential = await (await database.prepare('SELECT password_hash,discord_id FROM users WHERE id=?')).get(user.id);
|
||||
const credential = await (await database.prepare('SELECT discord_id FROM users WHERE id=?')).get(user.id);
|
||||
if (!credential?.discord_id) return fail(409, { discordUnlinkError: 'No Discord account is linked.' });
|
||||
if (!canRemoveAuthenticationMethod(await getAuthenticationMethods(database, user.id), 'discord')) {
|
||||
return fail(400, { discordUnlinkError: 'Add another sign-in method before unlinking Discord so you do not lose access.' });
|
||||
}
|
||||
if (typeof credential.password_hash === 'string' && !(await verifyPassword(credential.password_hash, password))) {
|
||||
return fail(400, { discordUnlinkError: 'Your password is incorrect.' });
|
||||
}
|
||||
await (await database.prepare('UPDATE users SET discord_id=NULL WHERE id=?')).run(user.id);
|
||||
await (await database.prepare('DELETE FROM discord_profiles WHERE user_id=?')).run(user.id);
|
||||
return { discordUnlinked: true };
|
||||
|
||||
@@ -5,18 +5,26 @@
|
||||
let { data, form } = $props();
|
||||
const user = $derived(data.user!);
|
||||
let discordLinkPending = $state(false);
|
||||
type AccountPanel = 'identity' | 'access' | 'connections' | 'account';
|
||||
let discordUnlinkConfirm = $state(false);
|
||||
type AccountPanel = 'identity' | 'access' | 'account';
|
||||
type AccessPanel = 'passkeys' | 'email' | 'local' | 'connections';
|
||||
type AvatarPanel = 'presets' | 'upload';
|
||||
function initialAccountPanel(): AccountPanel {
|
||||
if (form?.discordUnlinkError || data.oauthError || data.discordLinked || form?.discordUnlinked) return 'connections';
|
||||
if (form?.passwordError || form?.loginUsernameError || form?.localCredentialError || form?.emailError || form?.passkeyError || data.emailVerified) return 'access';
|
||||
if (form?.discordUnlinkError || data.oauthError || data.discordLinked || form?.discordUnlinked || form?.passwordError || form?.loginUsernameError || form?.localCredentialError || form?.emailError || form?.passkeyError || data.emailVerified) return 'access';
|
||||
if (form?.deleteError) return 'account';
|
||||
return 'identity';
|
||||
}
|
||||
function initialAccessPanel(): AccessPanel {
|
||||
if (form?.discordUnlinkError || data.oauthError || data.discordLinked || form?.discordUnlinked) return 'connections';
|
||||
if (form?.emailError || form?.emailVerificationSent || form?.emailRemoved || data.emailVerified) return 'email';
|
||||
if (form?.passwordError || form?.loginUsernameError || form?.localCredentialError || form?.localCredentialsAdded || form?.localCredentialsRemoved) return 'local';
|
||||
return 'passkeys';
|
||||
}
|
||||
function initialAvatarPanel(): AvatarPanel {
|
||||
return form?.avatarError ? 'upload' : 'presets';
|
||||
}
|
||||
let accountPanel = $state<AccountPanel>(initialAccountPanel());
|
||||
let accessPanel = $state<AccessPanel>(initialAccessPanel());
|
||||
let avatarPanel = $state<AvatarPanel>(initialAvatarPanel());
|
||||
let passkeySupported = $state(true);
|
||||
let passkeyPending = $state(false);
|
||||
@@ -61,7 +69,7 @@
|
||||
</section>
|
||||
{#if data.chooseDisplayName}<div class="account-alert"><b>IDENTITY SETUP</b><span>Choose the SHD.build display name other agents will see. Unicode characters are supported.</span></div>{/if}
|
||||
{#if data.discordLinked}<p class="success">Discord account linked successfully.</p>{/if}
|
||||
{#if form?.discordUnlinked}<p class="success">Discord account unlinked. You can continue signing in with your username and password.</p>{/if}
|
||||
{#if form?.discordUnlinked}<p class="success">Discord account unlinked. Your other configured sign-in methods remain active.</p>{/if}
|
||||
{#if data.oauthError}<div class="account-alert error-alert" role="alert"><b>{data.oauthError.title}</b><span>{data.oauthError.message}</span></div>{/if}
|
||||
<div class="profile-dashboard">
|
||||
<section class="profile-feed">
|
||||
@@ -75,7 +83,6 @@
|
||||
<div class="account-tabs" aria-label="Account controls" role="tablist">
|
||||
<button id="identity-tab" class:active={accountPanel === 'identity'} role="tab" aria-selected={accountPanel === 'identity'} aria-controls="identity-panel" onclick={() => accountPanel = 'identity'}>PROFILE</button>
|
||||
<button id="access-tab" class:active={accountPanel === 'access'} role="tab" aria-selected={accountPanel === 'access'} aria-controls="access-panel" onclick={() => accountPanel = 'access'}>ACCESS</button>
|
||||
<button id="connections-tab" class:active={accountPanel === 'connections'} role="tab" aria-selected={accountPanel === 'connections'} aria-controls="connections-panel" onclick={() => accountPanel = 'connections'}>LINKS</button>
|
||||
<button id="account-tab" class:active={accountPanel === 'account'} role="tab" aria-selected={accountPanel === 'account'} aria-controls="account-panel" onclick={() => accountPanel = 'account'}>ACCOUNT</button>
|
||||
</div>
|
||||
|
||||
@@ -112,6 +119,13 @@
|
||||
</div>
|
||||
{:else if accountPanel === 'access'}
|
||||
<div class="account-panel" id="access-panel" role="tabpanel" aria-labelledby="access-tab">
|
||||
<div class="access-subtabs" role="tablist" aria-label="Sign-in method controls">
|
||||
<button class:active={accessPanel === 'passkeys'} role="tab" aria-selected={accessPanel === 'passkeys'} onclick={() => accessPanel = 'passkeys'}><span>PASSKEYS</span><b>{data.passkeys.length}</b></button>
|
||||
<button class:active={accessPanel === 'email'} role="tab" aria-selected={accessPanel === 'email'} onclick={() => accessPanel = 'email'}><span>EMAIL</span><b>{data.emailIdentity ? 1 : 0}</b></button>
|
||||
<button class:active={accessPanel === 'local'} role="tab" aria-selected={accessPanel === 'local'} onclick={() => accessPanel = 'local'}><span>PASSWORD</span><b>{data.hasLocalCredentials ? 1 : 0}</b></button>
|
||||
<button class:active={accessPanel === 'connections'} role="tab" aria-selected={accessPanel === 'connections'} onclick={() => accessPanel = 'connections'}><span>LINKS</span><b>{data.discordConnection ? 1 : 0}</b></button>
|
||||
</div>
|
||||
{#if accessPanel === 'passkeys'}
|
||||
<p class="eyebrow">PREFERRED ACCESS</p><h2>PASSKEYS</h2>
|
||||
<p class="identity-note passkey-intro">Use Face ID, Touch ID, Windows Hello, a security key, or your password manager without entering a SHD.build password.</p>
|
||||
{#if data.passkeys.length}
|
||||
@@ -128,10 +142,12 @@
|
||||
<label class="field">PASSKEY NAME<input bind:value={passkeyName} maxlength="60" placeholder="e.g. Windows Hello" /></label>
|
||||
<button class="primary console-action" disabled={!passkeySupported || passkeyPending || !passkeyName.trim()} aria-busy={passkeyPending} onclick={registerPasskey}>{passkeyPending ? 'REGISTERING…' : 'ADD A PASSKEY →'}</button>
|
||||
{#if !passkeySupported}<p class="connection-warning">This browser does not support passkeys.</p>{/if}
|
||||
<hr/><p class="eyebrow">PASSWORDLESS RECOVERY</p><h2>MAGIC LINK EMAIL</h2>
|
||||
{:else if accessPanel === 'email'}
|
||||
<p class="eyebrow">PASSWORDLESS RECOVERY</p><h2>MAGIC LINK EMAIL</h2>
|
||||
{#if data.emailIdentity}<div class="verified-email"><div><b>{data.emailIdentity.email}</b><small>VERIFIED EMAIL IDENTITY</small></div><span>VERIFIED</span></div><PendingForm action="?/removeEmailIdentity"><button class="secondary console-action" disabled={data.authenticationMethods.total <= 1}>REMOVE MAGIC-LINK EMAIL</button></PendingForm>{/if}
|
||||
<PendingForm action="?/requestEmailVerification"><label class="field">{data.emailIdentity ? 'CHANGE VERIFIED EMAIL' : 'EMAIL ADDRESS'}<input name="email" type="email" value={data.emailIdentity?.email ?? ''} autocomplete="email" required /></label><small class="identity-note">We will email a verification link before this address can be used to sign in.</small>{#if form?.emailError}<p class="error">{form.emailError}</p>{/if}{#if form?.emailVerificationSent}<p class="success">Verification link sent. It expires in 15 minutes.</p>{/if}{#if data.emailVerified}<p class="success">Email verified. Magic-link sign-in is now active.</p>{/if}<button class="secondary console-action" disabled={!data.emailDeliveryConfigured}>SEND VERIFICATION LINK →</button></PendingForm>
|
||||
<hr/><p class="eyebrow">OPTIONAL FALLBACK</p><h2>USERNAME & PASSWORD</h2>
|
||||
{:else if accessPanel === 'local'}
|
||||
<p class="eyebrow">OPTIONAL FALLBACK</p><h2>USERNAME & PASSWORD</h2>
|
||||
{#if data.hasLocalCredentials}
|
||||
<PendingForm action="?/changeLoginUsername"><label class="field">PRIVATE LOGIN USERNAME<input name="username" value={user.loginUsername ?? ''} autocomplete="username" required /></label><small class="identity-note">Used only to sign in and visible to you and authorized ISAC operators.</small>{#if form?.loginUsernameError}<p class="error">{form.loginUsernameError}</p>{/if}<button class="secondary console-action">UPDATE LOGIN USERNAME →</button></PendingForm>
|
||||
<hr/><PendingForm action="?/changePassword"><label class="field">CURRENT PASSWORD<input name="currentPassword" type="password" autocomplete="current-password" required /></label><label class="field">NEW PASSWORD<input name="newPassword" type="password" autocomplete="new-password" minlength="12" required /></label><label class="field">CONFIRM PASSWORD<input name="confirmPassword" type="password" autocomplete="new-password" minlength="12" required /></label>{#if form?.passwordError}<p class="error">{form.passwordError}</p>{/if}<button class="primary console-action">UPDATE PASSWORD →</button></PendingForm>
|
||||
@@ -140,16 +156,27 @@
|
||||
<p class="account-method-empty">USERNAME AND PASSWORD SIGN-IN IS NOT CONFIGURED</p>
|
||||
<PendingForm action="?/addLocalCredentials"><label class="field">PRIVATE LOGIN USERNAME<input name="username" autocomplete="username" required /></label><label class="field">PASSWORD<input name="newPassword" type="password" autocomplete="new-password" minlength="12" required /></label><label class="field">CONFIRM PASSWORD<input name="confirmPassword" type="password" autocomplete="new-password" minlength="12" required /></label>{#if form?.localCredentialError}<p class="error">{form.localCredentialError}</p>{/if}{#if form?.localCredentialsAdded}<p class="success">Username and password sign-in added.</p>{/if}<button class="secondary console-action">ADD PASSWORD FALLBACK →</button></PendingForm>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if accountPanel === 'connections'}
|
||||
<div class="account-panel" id="connections-panel" role="tabpanel" aria-labelledby="connections-tab">
|
||||
<p class="eyebrow">CONNECTIONS</p><h2>SIGN-IN METHODS</h2>
|
||||
{:else}
|
||||
<p class="eyebrow">CONNECTIONS</p><h2>SIGN-IN PROVIDERS</h2>
|
||||
<div class="provider-stack">
|
||||
{#if data.discordConnection}
|
||||
<div class="connection-card discord-connection linked-connection"><div class="connection-copy"><img src="/discord-symbol.svg" alt="" aria-hidden="true" /><div><b>{data.discordConnection.displayName ?? data.discordConnection.username ?? 'DISCORD USER'}</b><small>{data.discordConnection.username ? `@${data.discordConnection.username} · ` : ''}ID {data.discordConnection.id}</small></div></div><span class="connection-state">LINKED</span></div>
|
||||
<PendingForm action="?/unlinkDiscord" class="unlink-console">{#if data.hasPassword}<label class="field">PASSWORD TO UNLINK<input name="password" type="password" autocomplete="current-password" required/></label>{/if}{#if !data.discordConnection.canUnlink}<p class="connection-warning">Discord is your only sign-in method. Add a passkey, verified email, or password in Access before unlinking.</p>{/if}{#if form?.discordUnlinkError}<p class="error">{form.discordUnlinkError}</p>{/if}<button class="secondary console-action" disabled={!data.discordConnection.canUnlink}>UNLINK DISCORD</button></PendingForm>
|
||||
<div class="connection-card discord-connection linked-connection provider-connection-card" class:confirming={discordUnlinkConfirm}>
|
||||
<div class="connection-copy"><img src="/discord-symbol.svg" alt="" aria-hidden="true" /><div><b>{data.discordConnection.displayName ?? data.discordConnection.username ?? 'DISCORD USER'}</b><small>{data.discordConnection.username ? `@${data.discordConnection.username} · ` : ''}ID {data.discordConnection.id}</small></div></div>
|
||||
<button class="connection-state provider-unlink-trigger" disabled={!data.discordConnection.canUnlink} aria-label="Unlink Discord" onclick={() => discordUnlinkConfirm = true}><span>LINKED</span><span>UNLINK</span></button>
|
||||
{#if discordUnlinkConfirm}
|
||||
<div class="provider-unlink-confirm"><div><b>UNLINK DISCORD?</b><p>Disconnect this identity from your SHD.build account?</p></div><div><button class="secondary" onclick={() => discordUnlinkConfirm = false}>CANCEL</button><PendingForm action="?/unlinkDiscord"><button class="primary">CONFIRM UNLINK</button></PendingForm></div></div>
|
||||
{/if}
|
||||
{#if !data.discordConnection.canUnlink}<p class="connection-warning provider-card-message">Discord is your only sign-in method. Add a passkey, verified email, or password before unlinking.</p>{/if}
|
||||
{#if form?.discordUnlinkError}<p class="error provider-card-message">{form.discordUnlinkError}</p>{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="connection-card discord-connection"><div class="connection-copy"><img src="/discord-symbol.svg" alt="" aria-hidden="true" /><div><b>DISCORD</b><small>OAuth identity provider</small></div></div><a class="discord-link-button" class:interaction-pending={discordLinkPending} aria-busy={discordLinkPending} href="/auth/discord?mode=link" onclick={beginDiscordLink}><img src="/discord-symbol.svg" alt="" aria-hidden="true" /><span>Link Discord</span></a></div>
|
||||
{/if}
|
||||
<div class="provider-placeholder"><span class="provider-monogram google-provider">G</span><div><b>GOOGLE</b><small>ACCOUNT LINKING PLACEHOLDER</small></div><em>PLANNED</em></div>
|
||||
<div class="provider-placeholder"><span class="provider-monogram apple-provider">A</span><div><b>APPLE</b><small>ACCOUNT LINKING PLACEHOLDER</small></div><em>PLANNED</em></div>
|
||||
<div class="provider-placeholder"><span class="provider-monogram steam-provider">S</span><div><b>STEAM</b><small>ACCOUNT LINKING PLACEHOLDER</small></div><em>PLANNED</em></div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="account-panel" id="account-panel" role="tabpanel" aria-labelledby="account-tab">
|
||||
|
||||
@@ -45,7 +45,27 @@
|
||||
<div class="divider"><span>OR EMAIL A ONE-TIME LINK</span></div>
|
||||
<PendingForm action="?/magicLink"><label class="field">VERIFIED EMAIL<input name="email" type="email" value={form?.email ?? ''} autocomplete="email" placeholder="agent@example.com" required /></label>{#if form?.magicLinkError}<p class="error">{form.magicLinkError}</p>{/if}{#if form?.magicLinkSent || data.magicLinkSent}<p class="success">If that email is linked to an agent, a sign-in link is on its way.</p>{/if}{#if data.magicError}<p class="error">{data.magicError}</p>{/if}<button class="secondary full" disabled={!data.emailDeliveryConfigured}>EMAIL MAGIC LINK →</button></PendingForm>
|
||||
<div class="divider"><span>OR CONTINUE WITH</span></div>
|
||||
<a class="discord" class:interaction-pending={discordPending} href="/auth/discord" aria-busy={discordPending} onclick={beginDiscordSignIn}><img src="/discord-symbol.svg" alt="" aria-hidden="true" /><span>Continue with Discord</span></a>
|
||||
<div class="signin-provider-rail" aria-label="External sign-in providers">
|
||||
<a class="signin-provider discord-provider" class:interaction-pending={discordPending} href="/auth/discord" aria-busy={discordPending} onclick={beginDiscordSignIn}>
|
||||
<span class="signin-provider-icon"><img src="/discord-symbol.svg" alt="" aria-hidden="true" /></span>
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
<button class="signin-provider google-signin-provider" type="button" disabled title="Google sign-in is planned" aria-label="Google sign-in — planned">
|
||||
<span class="signin-provider-icon"><img src="/google-g.svg" alt="" aria-hidden="true" /></span>
|
||||
<span>Google</span>
|
||||
<em>Planned</em>
|
||||
</button>
|
||||
<button class="signin-provider apple-signin-provider" type="button" disabled title="Apple sign-in is planned" aria-label="Apple sign-in — planned">
|
||||
<span class="signin-provider-icon"><img src="/apple-mark.svg" alt="" aria-hidden="true" /></span>
|
||||
<span>Apple</span>
|
||||
<em>Planned</em>
|
||||
</button>
|
||||
<button class="signin-provider steam-signin-provider" type="button" disabled title="Steam sign-in is planned" aria-label="Steam sign-in — planned">
|
||||
<span class="signin-provider-icon"><img src="/steam-mark.svg" alt="" aria-hidden="true" /></span>
|
||||
<span>Steam</span>
|
||||
<em>Planned</em>
|
||||
</button>
|
||||
</div>
|
||||
{#if data.oauthError}<p class="error">{data.oauthError}</p>{/if}
|
||||
<details class="password-fallback" open={Boolean(form?.loginError)}><summary>USE USERNAME AND PASSWORD</summary><PendingForm action="?/login"><label class="field">USERNAME<input name="username" value={form?.username ?? ''} autocomplete="username webauthn" placeholder="Agent handle" required /></label><label class="field">PASSWORD<input name="password" type="password" autocomplete="current-password" placeholder="••••••••••••" required /></label>{#if form?.loginError}<p class="error">{form.loginError}</p>{/if}<button class="secondary full">SIGN IN WITH PASSWORD →</button></PendingForm></details>
|
||||
<small>Passkeys and one-time email links are preferred. Password sign-in remains available as an optional fallback.</small>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-label="Apple">
|
||||
<path fill="currentColor" d="M17.05 12.54c-.03-3.05 2.49-4.53 2.61-4.6a5.6 5.6 0 0 0-4.41-2.39c-1.85-.2-3.65 1.11-4.59 1.11-.96 0-2.41-1.09-3.97-1.06a5.83 5.83 0 0 0-4.91 2.99c-2.13 3.69-.54 9.12 1.5 12.1 1.02 1.46 2.21 3.1 3.77 3.04 1.53-.06 2.11-.98 3.96-.98 1.83 0 2.38.98 3.98.94 1.64-.03 2.68-1.47 3.66-2.94a12.1 12.1 0 0 0 1.67-3.4 5.25 5.25 0 0 1-3.27-4.81ZM14.04 3.59A5.3 5.3 0 0 0 15.25 0a5.39 5.39 0 0 0-3.48 1.71 5.05 5.05 0 0 0-1.25 3.45 4.45 4.45 0 0 0 3.52-1.57Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 580 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" role="img" aria-label="Google">
|
||||
<path fill="#EA4335" d="M17.64 9.205c0-.638-.057-1.252-.164-1.841H9v3.481h4.844a4.14 4.14 0 0 1-1.797 2.715v2.259h2.909c1.702-1.567 2.684-3.878 2.684-6.614Z"/>
|
||||
<path fill="#4285F4" d="M9 18c2.43 0 4.468-.806 5.956-2.181l-2.909-2.259c-.806.54-1.836.86-3.047.86-2.344 0-4.328-1.585-5.037-3.714H.956v2.332A8.999 8.999 0 0 0 9 18Z"/>
|
||||
<path fill="#FBBC05" d="M3.963 10.706A5.41 5.41 0 0 1 3.681 9c0-.592.102-1.168.282-1.706V4.962H.956A8.996 8.996 0 0 0 0 9c0 1.452.347 2.827.956 4.038l3.007-2.332Z"/>
|
||||
<path fill="#34A853" d="M9 3.58c1.322 0 2.508.454 3.441 1.346l2.581-2.581C13.464.892 11.426 0 9 0A8.999 8.999 0 0 0 .956 4.962l3.007 2.332C4.672 5.165 6.656 3.58 9 3.58Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 776 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-label="Steam">
|
||||
<path fill="currentColor" d="M12 0a12 12 0 0 0-12 11.44l6.45 2.67a3.39 3.39 0 0 1 1.92-.58l2.88-4.18v-.06a4.52 4.52 0 1 1 4.52 4.52h-.1l-4.1 2.93a3.4 3.4 0 0 1-6.66.7L.3 15.53A12 12 0 1 0 12 0ZM8.3 18.89l-1.48-.61a2.55 2.55 0 0 0 4.77-.4 2.55 2.55 0 0 0-1.47-3.29 2.49 2.49 0 0 0-1.95.01l1.53.63a1.88 1.88 0 1 1-1.4 3.66Zm7.47-6.59a3.01 3.01 0 1 1 0-6.02 3.01 3.01 0 0 1 0 6.02Zm0-.75a2.26 2.26 0 1 0 0-4.52 2.26 2.26 0 0 0 0 4.52Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 535 B |
Reference in New Issue
Block a user