diff --git a/docs/DESIGN_SYSTEM.md b/docs/DESIGN_SYSTEM.md index df912dd..6f96be0 100644 --- a/docs/DESIGN_SYSTEM.md +++ b/docs/DESIGN_SYSTEM.md @@ -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. diff --git a/src/app.css b/src/app.css index 03b6f9b..6ae17b2 100644 --- a/src/app.css +++ b/src/app.css @@ -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} diff --git a/src/routes/account/+page.server.ts b/src/routes/account/+page.server.ts index bbe3a5f..1f0ccfb 100644 --- a/src/routes/account/+page.server.ts +++ b/src/routes/account/+page.server.ts @@ -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 }; diff --git a/src/routes/account/+page.svelte b/src/routes/account/+page.svelte index b6ef1e7..5405ccd 100644 --- a/src/routes/account/+page.svelte +++ b/src/routes/account/+page.svelte @@ -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(initialAccountPanel()); + let accessPanel = $state(initialAccessPanel()); let avatarPanel = $state(initialAvatarPanel()); let passkeySupported = $state(true); let passkeyPending = $state(false); @@ -61,7 +69,7 @@ {#if data.chooseDisplayName}{/if} {#if data.discordLinked}

Discord account linked successfully.

{/if} - {#if form?.discordUnlinked}

Discord account unlinked. You can continue signing in with your username and password.

{/if} + {#if form?.discordUnlinked}

Discord account unlinked. Your other configured sign-in methods remain active.

{/if} {#if data.oauthError}{/if}
@@ -75,7 +83,6 @@ @@ -112,6 +119,13 @@
{:else if accountPanel === 'access'} - {:else if accountPanel === 'connections'} - {:else}