The sign-in gate for the collections admin at admins.emojicopy.com. Four states: sign-in, session expired, connection trouble, sign-in failed.
Sign in for admin access.
You'll sign in at admins.emojicopy.com.
PrimaryButton variant="green" Β· ui/buttons/PrimaryButton.vue β the CTA below is its verbatim mirror (pill radius, 54px, green-500 β green-800 hover)ui/css/colors.css β every color below is a token value (light scheme), commented with its token name.admin-gate + .admin-card β the gate page + card, raw HTML/CSS (the admin backend loads none of the app's CSS)cdn.jsdelivr.net/joypixels 11.0 β never the system glyph<!-- Collections Admin β sign-in gate -->
<div class="admin-gate">
<!-- optional: the EmojiCopy wordmark (same asset the app header
uses); drop this <img> and nothing else moves -->
<img class="admin-gate__wordmark" src="/assets/emojicopy-wordmark.svg" alt="EmojiCopy">
<div class="admin-card">
<img class="admin-card__emoji"
src="https://cdn.jsdelivr.net/joypixels/assets/11.0/png/unicode/64/1f510.png" alt="π">
<h1 class="admin-card__title">Collections</h1>
<p class="admin-card__desc">Sign in for admin access.</p>
<button class="admin-card__cta" type="button">Sign in</button>
<p class="admin-card__note">You'll sign in at admins.emojicopy.com.</p>
</div>
</div>
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@900&family=Inter:wght@400;500;700&display=swap');
/* tokens β values mirror ui/css/colors.css (light scheme) */
:root {
--green-500: #85FC64; /* --c-button-green-bg */
--green-700: #20AD03; /* focus ring */
--green-800: #1A8407; /* --c-button-green-bg-hover */
--metal-50: #F4F5F7; /* --c-surface-cta */
--metal-100: #EAEBEF; /* --c-border-primary */
--metal-200: #DCDFE6; /* --c-button-disabled-bg */
--metal-400: #969CAC; /* --c-button-disabled-text */
--metal-500: #7C8292; /* --c-text-tertiary */
--metal-800: #353841; /* --c-text-secondary */
--metal-950: #0F1012; /* --c-text-primary */
--white: #FFFFFF; /* --c-surface-secondary */
--ff-inter: 'Inter', sans-serif;
--ff-dm-sans: 'DM Sans', sans-serif;
}
/* page */
.admin-gate {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 28px;
padding: 24px;
background: var(--metal-50);
font-family: var(--ff-inter);
}
.admin-gate__wordmark { height: 20px; width: auto; }
/* card */
.admin-card {
width: 100%;
max-width: 400px;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 40px 32px 32px;
text-align: center;
background: var(--white);
border: 1px solid var(--metal-100);
border-radius: 24px; /* --r-xl */
box-shadow: 0 16px 72px rgba(23,26,87,.10); /* --shadow-card */
}
.admin-card__emoji { width: 48px; height: 48px; }
.admin-card__title {
margin: 0;
font-family: var(--ff-dm-sans);
font-weight: 900;
font-size: 30px;
line-height: 1.1;
letter-spacing: -.03em;
color: var(--metal-950);
}
.admin-card__desc {
margin: 0;
font-size: 15px;
line-height: 1.6;
color: var(--metal-800);
max-width: 36ch;
}
.admin-card__note {
margin: 0;
font-size: 12px;
color: var(--metal-500);
}
/* CTA β verbatim mirror of ui/buttons/Button.vue variant:green +
ui/buttons/PrimaryButton.vue. If the admin ever shares the app's
components, delete this block and use <PrimaryButton variant="green">. */
.admin-card__cta {
width: 100%;
margin-top: 8px;
height: max(3.38em, 46px); /* PrimaryButton height β 54px at 16px */
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 max(1.77em, 8px);
border: none;
border-radius: 100px; /* PrimaryButton pill */
background: var(--green-500);
color: var(--metal-950);
font-family: var(--ff-inter);
font-weight: 700;
font-size: 16px;
cursor: pointer;
transition: background .2s, color .2s;
}
.admin-card__cta:hover { background: var(--green-800); color: var(--white); }
.admin-card__cta:focus-visible { outline: 2px solid var(--green-700); outline-offset: 2px; }
.admin-card__cta:disabled { background: var(--metal-200); color: var(--metal-400); cursor: not-allowed; }
Sign in for admin access.
Your session expired. Sign in again.
You'll sign in at admins.emojicopy.com.
FieldMessage types Β· ui/input/FieldMessage.vue β the notice reuses its info | warning | error vocabulary.admin-notice β new atom, three types, slots between description and CTA<div class="admin-card">
<img class="admin-card__emoji"
src="https://cdn.jsdelivr.net/joypixels/assets/11.0/png/unicode/64/1f510.png" alt="π">
<h1 class="admin-card__title">Collections</h1>
<p class="admin-card__desc">Sign in for admin access.</p>
<div class="admin-notice admin-notice--info" role="status">
<p class="admin-notice__text">Your session expired. Sign in again.</p>
</div>
<button class="admin-card__cta" type="button">Sign in</button>
<p class="admin-card__note">You'll sign in at admins.emojicopy.com.</p>
</div>
/* extra tokens used here β same source, ui/css/colors.css */
:root {
--metal-300: #B0B5C5;
--destructive-300: #CF6666; /* --c-text-destructive family */
--destructive-400: #AD1103;
--yellow-500: #FFC021; /* --c-icon-yellow */
}
/* notice β one atom, three types; names mirror FieldMessage.vue */
.admin-notice {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 16px;
border-radius: 12px; /* --r-md */
text-align: left;
}
.admin-notice__text {
margin: 0;
font-size: 13px;
font-weight: 500;
line-height: 1.5;
}
.admin-notice__action {
flex: none;
padding: 0;
border: none;
background: none;
font: inherit;
font-weight: 700;
color: inherit;
text-decoration: underline;
text-underline-offset: 3px;
cursor: pointer;
}
.admin-notice--info {
background: var(--metal-100); /* --c-surface-tertiary */
color: var(--metal-800);
}
.admin-notice--warning {
background: color-mix(in srgb, var(--yellow-500) 12%, transparent);
border: 1px solid color-mix(in srgb, var(--yellow-500) 30%, transparent);
color: #7a4d00;
}
.admin-notice--error {
background: color-mix(in srgb, var(--destructive-300) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--destructive-300) 28%, transparent);
color: var(--destructive-400);
}
Sign in for admin access.
Can't reach the sign-in server.
You'll sign in at admins.emojicopy.com.
FieldMessage types Β· ui/input/FieldMessage.vue β the notice reuses its info | warning | error vocabulary--c-icon-yellow #FFC021 β same mix the workspace's yellow callouts use; no new palette.admin-notice β new atom, three types; this card adds the inline __action<div class="admin-card">
<img class="admin-card__emoji"
src="https://cdn.jsdelivr.net/joypixels/assets/11.0/png/unicode/64/1f510.png" alt="π">
<h1 class="admin-card__title">Collections</h1>
<p class="admin-card__desc">Sign in for admin access.</p>
<div class="admin-notice admin-notice--warning" role="alert">
<p class="admin-notice__text">Can't reach the sign-in server.</p>
<button class="admin-notice__action" type="button">Retry</button>
</div>
<button class="admin-card__cta" type="button">Sign in</button>
<p class="admin-card__note">You'll sign in at admins.emojicopy.com.</p>
</div>
/* extra tokens used here β same source, ui/css/colors.css */
:root {
--metal-300: #B0B5C5;
--destructive-300: #CF6666; /* --c-text-destructive family */
--destructive-400: #AD1103;
--yellow-500: #FFC021; /* --c-icon-yellow */
}
/* notice β one atom, three types; names mirror FieldMessage.vue */
.admin-notice {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 16px;
border-radius: 12px; /* --r-md */
text-align: left;
}
.admin-notice__text {
margin: 0;
font-size: 13px;
font-weight: 500;
line-height: 1.5;
}
.admin-notice__action {
flex: none;
padding: 0;
border: none;
background: none;
font: inherit;
font-weight: 700;
color: inherit;
text-decoration: underline;
text-underline-offset: 3px;
cursor: pointer;
}
.admin-notice--info {
background: var(--metal-100); /* --c-surface-tertiary */
color: var(--metal-800);
}
.admin-notice--warning {
background: color-mix(in srgb, var(--yellow-500) 12%, transparent);
border: 1px solid color-mix(in srgb, var(--yellow-500) 30%, transparent);
color: #7a4d00;
}
.admin-notice--error {
background: color-mix(in srgb, var(--destructive-300) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--destructive-300) 28%, transparent);
color: var(--destructive-400);
}
Try again, or copy the details below for the team.
Token request failed: 400 β {"error":"invalid_grant","error_description":"The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.","hint":"Cannot decrypt the authorization code"}
PrimaryButton variant="green" Β· ui/buttons/PrimaryButton.vue β same CTA mirror as card A.admin-detail β mono payload box + copy chip.admin-card__back β quiet text button back to the gate<div class="admin-card">
<img class="admin-card__emoji"
src="https://cdn.jsdelivr.net/joypixels/assets/11.0/png/unicode/64/26a0.png" alt="β οΈ">
<h1 class="admin-card__title">Sign-in failed</h1>
<p class="admin-card__desc">Try again, or copy the details below for the team.</p>
<div class="admin-detail">
<pre class="admin-detail__body">Token request failed: 400 β {"error":"invalid_grant", β¦}</pre>
<button class="admin-detail__copy" type="button">Copy</button>
</div>
<button class="admin-card__cta" type="button">Try again</button>
<button class="admin-card__back" type="button">Back to sign-in</button>
</div>
/* payload box β replaces the raw JSON dump; scrolls past ~5 lines */
.admin-detail {
position: relative;
width: 100%;
text-align: left;
background: var(--metal-50); /* --c-surface-cta */
border: 1px solid var(--metal-100);
border-radius: 12px; /* --r-md */
}
.admin-detail__body {
margin: 0;
padding: 14px 16px;
max-height: 110px;
overflow: auto;
font-family: ui-monospace, 'Menlo', 'Consolas', monospace;
font-size: 11.5px;
line-height: 1.55;
color: var(--metal-800);
white-space: pre-wrap;
word-break: break-word;
}
.admin-detail__copy {
position: absolute;
top: 8px;
right: 8px;
padding: 4px 10px;
font-family: var(--ff-inter);
font-weight: 700;
font-size: 10px;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--metal-500);
background: var(--white);
border: 1px solid var(--metal-100);
border-radius: 100px;
cursor: pointer;
transition: color .15s, background .15s;
}
.admin-detail__copy:hover { color: var(--metal-950); }
.admin-detail__copy.is-copied {
background: var(--green-500);
border-color: var(--green-700);
color: var(--metal-950);
}
/* quiet text button back to the gate */
.admin-card__back {
margin: 0;
padding: 0;
border: none;
background: none;
font-family: var(--ff-inter);
font-weight: 500;
font-size: 14px;
color: var(--metal-500);
cursor: pointer;
}
.admin-card__back:hover {
color: var(--metal-950);
text-decoration: underline;
text-underline-offset: 3px;
}
document.querySelector('.admin-detail__copy').addEventListener('click', (e) => {
const body = document.querySelector('.admin-detail__body').textContent;
navigator.clipboard.writeText(body).then(() => {
e.target.textContent = 'Copied!';
e.target.classList.add('is-copied');
setTimeout(() => {
e.target.textContent = 'Copy';
e.target.classList.remove('is-copied');
}, 1600);
});
});