-
Notifications
You must be signed in to change notification settings - Fork 451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New achievements - YOLO, Pull Shark, Galaxy Brain #2
Comments
Haha, yeah, I just added a note for that in 0767c5f :D I'll (try to) add them in a few hours! |
research:
|
Example profiles:
|
Here's a gold level in galaxy brain: https://github.com/ayende?tab=achievements&achievement=galaxy-brain I searched a bit but couldn't find any additional achievements. |
Although it doesn't mean anything, having won it and having it appear on my profile made me very happy. I loved :') |
Blog Post regarding Github Achievements |
Are you also going to put together a list of people who got it as an example? |
Nono, that wasn't my intention - but for documenting it's good to have at least one example of each :) |
Ah yeah, my bad. I'm not clear enough. I mean one example of each. Glad you've answered it. |
Was resolvee in pr #3 |
Yes, thank you again - my intention was to close this issue soon, just doing some tinkering (like e.g. pulling the images in). |
For the record there is probably a tier after gold for 8192 pull requests but I looked at the top 50 GitHub users and couldn't find anyone with it. The pattern for pull shark seems to be 2ⁿ⁺³ for each new tier And for galaxy brain it seems like it's just 2ⁿ⁺¹ for each new tier, but excluding EDIT: Maybe not, see below :) |
Based on the HTML on e.g. https://github.com/ljharb?tab=achievements&achievement=pull-shark <achievement-badge-flip class="m-1 achievement-badges achievement-badges-flip" data-tier-count="4" data-action="mouseenter:achievement-badge-flip#flip" data-catalyst="">
<img src="https://github.githubassets.com/images/modules/profile/achievements/pull-shark-default.png" data-targets="achievement-badge-flip.tiers" width="140" alt="Achievement: Pull Shark" data-view-component="true" class="tier-badge">
<img src="https://github.githubassets.com/images/modules/profile/achievements/pull-shark-bronze.png" data-targets="achievement-badge-flip.tiers" width="140" alt="Achievement: Pull Shark" data-view-component="true" class="tier-badge tier-badge--back">
<img src="https://github.githubassets.com/images/modules/profile/achievements/pull-shark-silver.png" data-targets="achievement-badge-flip.tiers" width="140" alt="Achievement: Pull Shark" data-view-component="true" class="tier-badge">
<img src="https://github.githubassets.com/images/modules/profile/achievements/pull-shark-gold.png" data-targets="achievement-badge-flip.tiers" width="140" alt="Achievement: Pull Shark" data-view-component="true" class="tier-badge tier-badge--back">
</achievement-badge-flip> I tried to meta-hack that a bit, but didn't mange to find anything. For
I tried like "platinum", "diamond", etc., but no dice so far, so... Let me know if you find something though! :D |
I think I got confirmation both for nothing being there but also just not yet!
achievements.ts source mapped from profile-7e4a89a2012a.js
import {controller, attr, targets} from '@github/catalyst'
// eslint-disable-next-line no-restricted-imports
import {on} from 'delegated-events'
import {pushState} from '../history'
function updateAchievementParameter(event: Event & {currentTarget: Element}) {
const details = event.currentTarget
const slug = details.getAttribute('data-achievement-slug')
const url = new URL(window.location.href, window.location.origin)
const params = new URLSearchParams(url.search)
if (details.hasAttribute('open') && slug) {
params.set('achievement', slug)
} else {
params.delete('achievement')
}
url.search = params.toString()
pushState(null, '', url.toString())
}
on('toggle', '.js-achievement-card-details', updateAchievementParameter, {capture: true})
// Catalyst component to use a "coin flip" animation to display all unlocked tiers of an earned achievement.
//
// Expects a data-tier-count attribute to be set to the number of tier images available as children within the
// DOM. Each bage tier image must be annotated as a target for the tiers property. A flip will occur as soon as
// the component is loaded into the DOM and may be repeated by triggering the "flip" action.
//
// Usage:
//
// <achievement-badge-flip data-tier-count="3">
// <img class="tier-badge" data-targets="achievement-badge-flip.tiers" />
// <img class="tier-badge tier-badge--back" data-targets="achievement-badge-flip.tiers" />
// <img class="tier-badge" data-targets="achievement-badge-flip.tiers" />
// </achievement-badge-flip>
@controller
class AchievementBadgeFlipElement extends HTMLElement {
@attr tierCount = 0
@targets tiers: HTMLElement[]
animations: Map<HTMLElement, Animation> = new Map()
// To implement the achievement badge flipping animation, we need to identify which keyframes -- specified as
// progress percentages through the animation duration, [0..100] -- at which each badge tier graphic should toggle
// its visibility. These correspond to the x-values of the cubic bezier curve used by the flipping animation at which
// its y-value corresponds to a property value that's a multiple of 180 degrees.
//
// For example: With four achievement tiers to display, we have an animation that progresses the rotateY property
// from 0 to 720 degrees. We need each tier graphic's animation to have keyframes for its opacity property at the
// 0, 180, 360, 540, and finally 720 degree points, which occur at bezier curve y-values of 0, 0.25, 0.5, 0.75, and 1.
// Using the inverse of the cubic bezier easing function, we can calculate that these occur at x-values of
// 0, 9, 22, 42, and 100, so these are the keyframes that we create.
//
// To calculate the inverse of the cubic bezier function, I used the bezier-easing package from npm:
// https://github.com/gre/bezier-easing
//
// As written, it can only output y-values corresponding to x-values, but we can invert the curve be swapping x and
// y values within our control points: https://github.com/gre/bezier-easing/issues/38
//
// ```
// mkdir temp && npm init -y && npm install bezier-easing
// ```
//
// Then, in a node repl:
//
// ```
// const BezierEasing = require("bezier-easing")
// const inverse = BezierEasing(0, 0, 1, 0.25) // Note swapped x and y values from easing function in animation
//
// inverse(0 / 720) // === 0
// inverse(180 / 720) // === 0.09
// inverse(360 / 720) // === 0.22
// inverse(540 / 720) // === 0.42
// inverse(720 / 720) // === 1
// ```
BADGE_SIDE_KEYFRAMES = [
// zero sides. (unused)
[],
// one side. (unused, we don't show half a flip)
// rotateY values: 0, 180
// y-values: 0, 1
[0, 1],
// two sides.
// rotateY values: 0, 180, 360
// bezier curve y-values: 0, 0.5, 1
[0, 0.22, 1],
// three sides.
// rotateY values: 0, 180, 360, 540
// bezier curve y-values: 0, 0.33, 0.66, 1
[0, 0.13, 0.34, 1],
// four sides.
// rotateY values: 0, 180, 360, 540, 720
// bezier curve y-values: 0, 0.25, 0.5, 0.75, 1
[0, 0.09, 0.22, 0.42, 1],
// five sides. (currently unused, we have no five-tier achievements yet)
// rotateY values: 0, 180, 360, 540, 720, 900
[0, 0.07, 0.16, 0.29, 0.47, 1]
]
// Create a "flip" animation on the container element and opacity animations on each tier badge image.
connectedCallback() {
// No flipping for single-tier achievements.
if (this.tierCount <= 1) {
return
}
// Web animations API unavailable.
if (!this.animate) {
return
}
// This animation vertically rotates all badge images. The number of "flips" is governed by the maxRotation
// property, one flip of 180 degrees for each unlocked tier.
const containerAnimation = this.animate(
[{transform: 'rotateY(0deg)'}, {transform: `rotateY(${this.maxRotation}deg)`}],
{
duration: this.duration,
easing: 'cubic-bezier(0, 0, 0.25, 1)'
}
)
this.animations.set(this, containerAnimation)
// Create opacity animations for each tier badge image. If the expected child DOM elements are not yet loaded at
// the time when the component is attached, use a MutationObserver to create animations for them when they are.
if (!this.createTierAnimations()) {
const observer = new MutationObserver((mutationList, obs) => {
if (this.createTierAnimations()) {
obs.disconnect()
}
})
observer.observe(this, {childList: true})
}
}
// Compute the common duration of all animations managed by this component: 500ms times the number of tiers.
get duration() {
return this.tierCount * 500
}
// Compute the maximum container rotation in degrees.
get maxRotation() {
return this.tierCount * 180
}
// Create new opacity animations for any child badge images that do not have them yet. Returns true if all
// expected child elements are available in the DOM or false if more are expected to arrive.
createTierAnimations(): boolean {
for (const tierElement of this.tiers) {
this.ensureTierAnimation(tierElement)
}
return this.tiers.length >= this.tierCount
}
// Create an opacity animation for a single child badge image if it has not been created yet. This animation will
// ensure that the badge is visible during the correct "flip" it's expected to be.
ensureTierAnimation(childElement: HTMLElement) {
if (this.animations.has(childElement)) {
return
}
const childIndex = this.tiers.indexOf(childElement)
if (childIndex < 0) {
return
}
const offsets = this.BADGE_SIDE_KEYFRAMES[this.tierCount]
if (!offsets) {
return
}
// When flipping an odd number of tiers, we need to reverse the first tier image during the final flip so that
// it's correctly visible as the back of the last tier.
const hasOddTiers = this.tierCount % 2 === 1
const keyframes = offsets.map((offset, index) => {
// Each tier image should be visible during the flip keyframes corresponding to its tier and the one immediately
// after (so it appears as the "back" of the next flip). The first tier image must also be visible during the
// final flip, so it correctly appears as the back of the final one.
const beVisible =
index === childIndex || index === childIndex + 1 || (childIndex === 0 && index === this.tierCount)
const keyframe: Keyframe = {offset, opacity: beVisible ? 1 : 0, easing: 'step-start'}
if (hasOddTiers && childIndex === 0) {
// Animate the transform of the first child: 0 degrees of y-axis rotation on all keyframes except the final
// one, when it's 180 degrees to act as the "back" of the last tier flip.
const rotation = index === offsets.length - 1 ? 180 : 0
keyframe.transform = `rotateY(${rotation}deg)`
}
return keyframe
})
const tierAnimation = childElement.animate(keyframes, {duration: this.duration})
this.animations.set(childElement, tierAnimation)
}
// Public action method. Trigger this to restart all animations as long as none are already running.
flip() {
for (const animation of this.animations.values()) {
if (animation.playState === 'running') {
return
}
}
for (const animation of this.animations.values()) {
animation.play()
}
}
} profile-3eb3d4e134d4.css.pinned-item-list-item .pinned-item-handle {
color: var(--color-fg-muted)
}
.pinned-item-list-item .pinned-item-handle:hover {
cursor: grab
}
.pinned-item-list-item.is-dragging,.pinned-item-list-item.is-dragging .pinned-item-handle {
cursor: grabbing
}
.pinned-item-list-item.is-dragging {
background-color: var(--color-accent-subtle)
}
.pinned-item-list-item.sortable-ghost {
background-color: var(--color-accent-subtle);
opacity: 0
}
.pinned-item-list-item.empty {
border-style: dashed;
border-width: 1px;
align-items: center;
justify-content: center
}
.pinned-item-list-item-content {
display: flex;
width: 100%;
flex-direction: column
}
.pinned-item-desc {
flex: 1 0 auto
}
.pinned-item-meta {
display: inline-block
}
.pinned-item-meta+.pinned-item-meta {
margin-left: 16px
}
.achievement-badge-sidebar {
filter: drop-shadow(var(--color-shadow-large))
}
.achievement-badge-card {
width: 96px;
margin: 4px;
filter: drop-shadow(var(--color-shadow-large))
}
@media(min-width: 1012px) {
.achievement-badge-card {
width:128px
}
}
.achievement-tier-label {
color: var(--color-scale-gray-9)
}
.achievement-tier-label--bronze {
background-color: #f9bfa7;
border-color: #f9bfa7
}
.achievement-tier-label--silver {
background-color: #e1e4e4;
border-color: #e1e4e4
}
.achievement-tier-label--gold {
background-color: #fae57e;
border-color: #fae57e
}
.achievement-card {
box-shadow: var(--color-shadow-medium);
transition: .4s ease
}
.achievement-card:hover {
box-shadow: var(--color-shadow-large)
}
.achievement-card-unseen {
box-shadow: 0 3px 6px var(--color-accent-subtle)
}
.achievement-badges .tier-badge {
padding: 0;
border: 0
}
.achievement-badges-flip {
position: relative;
transform-style: preserve-3d;
width: 140px;
height: 140px
}
.achievement-badges-flip .tier-badge {
position: absolute;
top: 0;
left: 0;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
opacity: 0
}
.achievement-badges-flip .tier-badge--back {
transform: rotateY(180deg)
}
.achievement-badges-flip .tier-badge:first-child,.achievement-badges-flip .tier-badge:nth-child(2) {
opacity: 1
}
.achievement-detail-close {
width: 32px;
height: 32px
}
.achievement-history {
margin-bottom: -16px
}
.achievement-history .achievement-history-unlocked-at::before {
top: 24px
}
.achievement-history .achievement-history-tier:last-child::before {
bottom: 40px
}
.contributions-setting-menu {
z-index: 80;
width: 330px
}
.ContributionCalendar.days-selected .ContributionCalendar-day {
opacity: .5
}
.ContributionCalendar.days-selected .ContributionCalendar-day.active {
opacity: 1
}
.ContributionCalendar-label {
font-size: 12px;
fill: var(--color-fg-default)
}
.ContributionCalendar-day-private-profile {
fill: var(--color-canvas-subtle)
}
.ContributionCalendar-day,.ContributionCalendar-day[data-level="0"] {
fill: var(--color-calendar-graph-day-bg);
shape-rendering: geometricPrecision;
outline: 1px solid var(--color-calendar-graph-day-border);
outline-offset: -1px
}
.ContributionCalendar-day[data-level="1"] {
fill: var(--color-calendar-graph-day-L1-bg);
outline: 1px solid var(--color-calendar-graph-day-L1-border)
}
.ContributionCalendar-day[data-level="2"] {
fill: var(--color-calendar-graph-day-L2-bg);
outline: 1px solid var(--color-calendar-graph-day-L2-border)
}
.ContributionCalendar-day[data-level="3"] {
fill: var(--color-calendar-graph-day-L3-bg);
outline: 1px solid var(--color-calendar-graph-day-L3-border)
}
.ContributionCalendar-day[data-level="4"] {
fill: var(--color-calendar-graph-day-L4-bg);
outline: 1px solid var(--color-calendar-graph-day-L4-border)
}
.ContributionCalendar[data-holiday=halloween] .ContributionCalendar-day[data-level="1"] {
fill: var(--color-calendar-halloween-graph-day-L1-bg)
}
.ContributionCalendar[data-holiday=halloween] .ContributionCalendar-day[data-level="2"] {
fill: var(--color-calendar-halloween-graph-day-L2-bg)
}
.ContributionCalendar[data-holiday=halloween] .ContributionCalendar-day[data-level="3"] {
fill: var(--color-calendar-halloween-graph-day-L3-bg)
}
.ContributionCalendar[data-holiday=halloween] .ContributionCalendar-day[data-level="4"] {
fill: var(--color-calendar-halloween-graph-day-L4-bg)
}
.graph-before-activity-overview {
border-top-left-radius: 6px;
border-top-right-radius: 6px
}
.activity-overview-box {
border-top-left-radius: 0;
border-top-right-radius: 0
}
.contribution-activity .select-menu-button {
position: relative;
top: -4px
}
.contribution-activity.loading .contribution-activity-listing {
display: none
}
.contribution-activity.loading .contribution-activity-show-more {
display: none
}
.contribution-activity.loading .contribution-activity-spinner {
display: block
}
.contribution-activity-spinner {
display: none
}
li.contribution {
padding: 8px 0;
list-style: none
}
li.contribution h3 {
display: inline-block;
margin: 0;
font-size: 14px
}
li.contribution .cmeta {
display: block;
font-size: 12px
}
li.contribution .d {
color: var(--color-fg-default)
}
li.contribution .a {
color: var(--color-fg-default)
}
li.contribution .num {
color: var(--color-fg-muted)
}
.user-profile-sticky-bar::after {
height: 48px
}
.user-profile-sticky-bar {
position: fixed;
top: 0;
z-index: 90;
width: 233px;
word-break: break-all;
pointer-events: none;
opacity: 0;
transition: .2s
}
.user-profile-sticky-bar.is-stuck {
pointer-events: auto;
opacity: 1
}
.user-profile-nav .UnderlineNav-item {
margin-right: 0 !important;
line-height: 30px;
white-space: nowrap
}
.user-profile-nav {
background-color: var(--color-canvas-default);
border-bottom: 1px solid var(--color-border-default);
box-shadow: none
}
.user-profile-nav.is-stuck {
z-index: 90
}
.user-profile-mini-vcard {
position: relative;
top: 1px;
z-index: 110;
height: 48px
}
.user-profile-mini-avatar {
width: 32px
}
.page-profile .news {
float: none;
width: auto
}
.mini-follow-button {
padding: 0 8px;
line-height: 1.5;
opacity: 0;
transition: opacity .2s
}
.is-follow-stuck .mini-follow-button {
opacity: 1
}
.user-status-circle-badge-container {
position: absolute;
bottom: 0;
left: 100%;
z-index: 2;
width: 38px;
height: 38px;
margin-bottom: 32px;
margin-left: -40px
}
.user-status-circle-badge-container .user-status-emoji-container {
width: 20px;
height: 20px;
margin-right: 0 !important
}
.user-status-circle-badge-container .user-status-message-wrapper {
width: 0;
padding-top: 0 !important;
overflow: hidden;
line-height: 20px;
opacity: 0;
transition: .1s ease
}
.user-status-circle-badge-container .user-status-busy {
background-color: var(--color-canvas-default) !important;
background-image: linear-gradient(var(--color-attention-subtle), var(--color-attention-subtle))
}
.user-status-circle-badge-container.user-status-editable:hover,.user-status-circle-badge-container.user-status-has-content:hover {
width: auto;
max-width: 544px
}
.user-status-circle-badge-container.user-status-editable:hover .user-status-emoji-container,.user-status-circle-badge-container.user-status-has-content:hover .user-status-emoji-container {
margin-right: 8px !important
}
.user-status-circle-badge-container.user-status-editable:hover .user-status-message-wrapper,.user-status-circle-badge-container.user-status-has-content:hover .user-status-message-wrapper {
width: 100%;
opacity: 1
}
.user-status-circle-badge-container.user-status-editable:hover .user-status-circle-badge,.user-status-circle-badge-container.user-status-has-content:hover .user-status-circle-badge {
box-shadow: var(--color-shadow-medium)
}
.user-status-circle-badge-container .user-status-message-wrapper .team-mention,.user-status-circle-badge-container .user-status-message-wrapper .user-mention {
white-space: nowrap
}
.vcard-names-container {
position: sticky;
top: 0
}
.vcard-names-container.is-stuck {
pointer-events: none
}
.vcard-names-container.is-stuck .vcard-names {
opacity: 0
}
.vcard-names-container.is-stuck::after {
opacity: 1
}
.vcard-names {
line-height: 1
}
.vcard-details {
list-style: none
}
.vcard-details .css-truncate.css-truncate-target {
width: 100%;
max-width: 100%
}
.vcard-details .css-truncate.css-truncate-target div {
overflow: hidden;
text-overflow: ellipsis
}
.vcard-detail {
padding-left: 24px;
font-size: 14px
}
.vcard-detail .octicon {
float: left;
width: 16px;
margin-top: 4px;
margin-left: -24px;
color: var(--color-fg-muted);
text-align: center
}
.user-profile-bio {
overflow: hidden;
font-size: 14px
}
/*# sourceMappingURL=profile-85b08d94c18a.css.map*/ |
Alright, finished for now. Put potential additional TODOs in #5. Gonna close this one. |
loks nice |
yea looks nice i want to try once 🤣 |
There were new achievements added in June 2022:
The text was updated successfully, but these errors were encountered: