Skip to content
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

Closed
Erol444 opened this issue Jun 9, 2022 · 18 comments
Closed

New achievements - YOLO, Pull Shark, Galaxy Brain #2

Erol444 opened this issue Jun 9, 2022 · 18 comments

Comments

@Erol444
Copy link

Erol444 commented Jun 9, 2022

There were new achievements added in June 2022:

  • YOLO (merge PR without a review)
  • Pull Shark (Open PR that has been merged)
  • Galaxy Brain (I think some answers?)
@Schweinepriester
Copy link
Owner

Schweinepriester commented Jun 9, 2022

Haha, yeah, I just added a note for that in 0767c5f :D

I'll (try to) add them in a few hours!

@sersorrel
Copy link

research:

  • Pull Shark unlocks after the second PR is merged; you get bronze after 16, silver after 128, and gold after 1024
  • YOLO is just a one-off, no bronze/silver/gold
  • Galaxy Brain unlocks after two accepted answers, bronze after 8; I haven't found anyone with any higher than bronze yet

@Schweinepriester
Copy link
Owner

Example profiles:

@jonwiggins
Copy link

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.

@rroderickk
Copy link

Although it doesn't mean anything, having won it and having it appear on my profile made me very happy. I loved :')
thanks github
🤖🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀

@prateek-chaubey
Copy link

@ilhambara
Copy link

Example profiles:

Are you also going to put together a list of people who got it as an example?

@Schweinepriester
Copy link
Owner

Example profiles:

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 :)

@ilhambara
Copy link

Example profiles:

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.

@farkon00
Copy link
Contributor

farkon00 commented Jun 9, 2022

Was resolvee in pr #3

@Schweinepriester
Copy link
Owner

Yes, thank you again - my intention was to close this issue soon, just doing some tinkering (like e.g. pulling the images in).

@MarcusOtter
Copy link

MarcusOtter commented Jun 9, 2022

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
2¹ = Unlocked
2⁴ = Bronze
2⁷ = Silver
2¹⁰ = Gold
2¹³ = ???
2¹⁶ = ???

And for galaxy brain it seems like it's just 2ⁿ⁺¹ for each new tier, but excluding 4 for some reason 😄

EDIT: Maybe not, see below :)

@Schweinepriester
Copy link
Owner

Based on the HTML on e.g. https://github.com/ljharb?tab=achievements&achievement=pull-shark
image

<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

https://github.githubassets.com/images/modules/profile/achievements/pull-shark-<INSERT-HERE>.png

I tried like "platinum", "diamond", etc., but no dice so far, so...

Let me know if you find something though! :D

@Schweinepriester
Copy link
Owner

I think I got confirmation both for nothing being there but also just not yet!
Also places we can look in the future 👀

// five sides. (currently unused, we have no five-tier achievements yet)

image

image

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*/

@Schweinepriester
Copy link
Owner

Schweinepriester commented Jun 10, 2022

Alright, finished for now. Put potential additional TODOs in #5. Gonna close this one.
Thanks everyone for the collaboration! :)

@pablodz
Copy link

pablodz commented Jun 10, 2022

loks nice

@sanikava
Copy link

yea looks nice i want to try once 🤣

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests