mirror of
https://github.com/decompme/decomp.me.git
synced 2025-12-19 03:31:00 -06:00
Holiday Frogs ❤️🍀🎃🦃🎅 (#1766)
* Holiday Frogs ❤️🍀🎃🦃🎅 * Rework to make it easier to inject a different Frog
This commit is contained in:
@@ -2,7 +2,7 @@ import type { ReactNode } from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
import Frog from "@/components/Nav/frog.svg";
|
||||
import Frog from "@/components/Frog/Frog";
|
||||
|
||||
const subtitle = "mt-8 text-xl font-semibold tracking-tight text-gray-11";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ChevronRightIcon } from "@primer/octicons-react";
|
||||
|
||||
import GhostButton from "@/components/GhostButton";
|
||||
import Frog from "@/components/Nav/frog.svg";
|
||||
import Frog from "@/components/Frog/Frog";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@ import { MarkGithubIcon } from "@primer/octicons-react";
|
||||
|
||||
import DiscordIcon from "./discord.svg";
|
||||
import GhostButton from "./GhostButton";
|
||||
import Logotype from "./Logotype";
|
||||
import SiteLogo from "./Nav/SiteLogo";
|
||||
|
||||
function Separator() {
|
||||
return <div className="hidden h-4 w-px bg-gray-6 sm:inline-block" />;
|
||||
@@ -20,7 +20,7 @@ export default function Footer() {
|
||||
<div className="border-gray-6 border-t py-10">
|
||||
<div className="flex items-center justify-center">
|
||||
<Link href="/">
|
||||
<Logotype />
|
||||
<SiteLogo />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="mt-4 flex flex-col items-center justify-center gap-1 sm:flex-row sm:gap-2">
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import type { SVGProps } from "react";
|
||||
|
||||
import clsx from "clsx";
|
||||
|
||||
import type * as api from "@/lib/api";
|
||||
|
||||
import Frog from "../Nav/frog.svg";
|
||||
|
||||
import styles from "./AnonymousFrog.module.scss";
|
||||
import Frog from "./Frog";
|
||||
|
||||
export type Props = SVGProps<SVGElement> & {
|
||||
user: api.AnonymousUser;
|
||||
@@ -18,11 +14,11 @@ export default function AnonymousFrogAvatar({
|
||||
className,
|
||||
...props
|
||||
}: Props) {
|
||||
const accentStyle = {
|
||||
"--accent-hue": user.frog_color[0],
|
||||
"--accent-saturation": user.frog_color[1],
|
||||
"--accent-lightness": user.frog_color[2],
|
||||
};
|
||||
const [hue, saturation, lightness] = user.frog_color;
|
||||
|
||||
const primary = `hsl(${hue}, ${saturation * 100}%, ${lightness * 100}%)`;
|
||||
const secondary = `hsl(${hue}, ${(0.3 * (1 - saturation) + saturation) * 100}%, ${(0.5 * (1 - lightness) + lightness) * 100}%)`;
|
||||
const nose = `hsl(${hue}, ${(saturation - 0.2 * saturation) * 100}%, ${(lightness - 0.4 * lightness) * 100}%)`;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -32,8 +28,10 @@ export default function AnonymousFrogAvatar({
|
||||
)}
|
||||
>
|
||||
<Frog
|
||||
style={accentStyle}
|
||||
className={clsx(styles.anonymousFrog, "h-4/6 w-4/6")}
|
||||
primary={primary}
|
||||
secondary={secondary}
|
||||
nose={nose}
|
||||
className="h-4/6 w-4/6"
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
31
frontend/src/components/Frog/Frog.tsx
Normal file
31
frontend/src/components/Frog/Frog.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from "react";
|
||||
|
||||
const Frog = ({
|
||||
primary = "#951fd9",
|
||||
secondary = "#cc87f4",
|
||||
pupil = "#000",
|
||||
eye = "#FFF",
|
||||
nose = "#505050",
|
||||
title = "",
|
||||
...props
|
||||
}) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" {...props}>
|
||||
<title>{title}</title>
|
||||
<path
|
||||
fill={secondary}
|
||||
d="M36 22c0 7.456-8.059 12-18 12S0 29.456 0 22 8.059 7 18 7s18 7.544 18 15z"
|
||||
/>
|
||||
<path
|
||||
fill={primary}
|
||||
d="M31.755 12.676C33.123 11.576 34 9.891 34 8c0-3.313-2.687-6-6-6-2.861 0-5.25 2.004-5.851 4.685-1.288-.483-2.683-.758-4.149-.758-1.465 0-2.861.275-4.149.758C13.25 4.004 10.861 2 8 2 4.687 2 2 4.687 2 8c0 1.891.877 3.576 2.245 4.676C1.6 15.356 0 18.685 0 22c0 7.456 8.059 1 18 1s18 6.456 18-1c0-3.315-1.6-6.644-4.245-9.324z"
|
||||
/>
|
||||
<circle fill={eye} cx="7.5" cy="7.5" r="3.5" className="eyeL" />
|
||||
<circle fill={pupil} cx="7.5" cy="7.5" r="1.5" className="pupilL" />
|
||||
<circle fill={eye} cx="28.5" cy="7.5" r="3.5" className="eyeR" />
|
||||
<circle fill={pupil} cx="28.5" cy="7.5" r="1.5" className="pupilR" />
|
||||
<circle fill={nose} cx="14" cy="20" r="1" className="noseL" />
|
||||
<circle fill={nose} cx="22" cy="20" r="1" className="noseR" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default Frog;
|
||||
66
frontend/src/components/Frog/HolidayFrog.tsx
Normal file
66
frontend/src/components/Frog/HolidayFrog.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
import Frog from "./Frog";
|
||||
|
||||
interface HolidayFrogProps {
|
||||
today?: Date;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function HolidayFrog({
|
||||
today = new Date(),
|
||||
className = "size-7",
|
||||
}: HolidayFrogProps) {
|
||||
const month = today.getUTCMonth();
|
||||
const date = today.getUTCDate();
|
||||
|
||||
// TODO: special handling for frug
|
||||
|
||||
// Defaults
|
||||
let primary = "#951fd9";
|
||||
let secondary = "#cc87f4";
|
||||
let message = "Happy Decomping!";
|
||||
|
||||
if (month === 1 && date === 14) {
|
||||
primary = "#e6396f";
|
||||
secondary = "#f7a1c4";
|
||||
message = "❤️ Happy Valentine's Day! ❤️";
|
||||
}
|
||||
if (month === 2 && date === 17) {
|
||||
primary = "#2a9d34";
|
||||
secondary = "#7fd28d";
|
||||
message = "🍀 Happy St Patrick's Day! 🍀";
|
||||
}
|
||||
if (month === 9 && date === 31) {
|
||||
primary = "#d96516";
|
||||
secondary = "#20150aff";
|
||||
message = "🎃 Happy Halloween! 🎃";
|
||||
}
|
||||
|
||||
if (month === 10) {
|
||||
// Thanksgiving (4th Thursday of November)
|
||||
const firstDay = new Date(today.getFullYear(), 10, 1);
|
||||
const firstThursday = 1 + ((4 - firstDay.getDay() + 7) % 7);
|
||||
const fourthThursday = firstThursday + 21;
|
||||
if (date === fourthThursday) {
|
||||
primary = "#9e682a";
|
||||
secondary = "#d2a679";
|
||||
message = "🦃 Happy Thanksgiving! 🦃";
|
||||
}
|
||||
}
|
||||
|
||||
if (month === 11 && date === 25) {
|
||||
primary = "#E61A1A";
|
||||
secondary = "#F8BFBF";
|
||||
message = "🎅 Merry Christmas! 🎅";
|
||||
}
|
||||
|
||||
return (
|
||||
<Frog
|
||||
className={className}
|
||||
aria-label="Purple frog"
|
||||
primary={primary}
|
||||
secondary={secondary}
|
||||
title={message}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import Frog from "./Nav/frog.svg";
|
||||
|
||||
export default function Logotype() {
|
||||
return (
|
||||
<div
|
||||
className="inline-flex items-center space-x-2"
|
||||
aria-label="decomp.me logo"
|
||||
>
|
||||
<Frog className="size-7" aria-label="Purple frog" />
|
||||
<span className="font-semibold text-xl leading-6 tracking-tight">
|
||||
decomp.me
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -64,47 +64,6 @@ $padding: 8px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:not(:hover) {
|
||||
@keyframes blink {
|
||||
0% {
|
||||
transform: scaleY(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: scaleY(0.2);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scaleY(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: scaleY(0.2);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scaleY(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.frog_svg__pupilR),
|
||||
:global(.frog_svg__pupilL),
|
||||
:global(.frog_svg__eyeR),
|
||||
:global(.frog_svg__eyeL) {
|
||||
transform-origin: 0 7px;
|
||||
animation: blink 0.4s 2s ease;
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ThreeBarsIcon, XIcon } from "@primer/octicons-react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import GhostButton from "../GhostButton";
|
||||
import Logotype from "../Logotype";
|
||||
import SiteLogo from "./SiteLogo";
|
||||
|
||||
import LoginState from "./LoginState";
|
||||
import styles from "./Nav.module.scss";
|
||||
@@ -71,7 +71,7 @@ export default function Nav({ children }: Props) {
|
||||
href="/"
|
||||
className="transition-colors hover:text-gray-12 active:translate-y-px"
|
||||
>
|
||||
<Logotype />
|
||||
<SiteLogo />
|
||||
</Link>
|
||||
</li>
|
||||
<li className={styles.headerItemLoginState}>
|
||||
@@ -108,7 +108,7 @@ export default function Nav({ children }: Props) {
|
||||
<ul className={styles.links}>
|
||||
<li className="flex items-center justify-center">
|
||||
<Link href="/">
|
||||
<Logotype />
|
||||
<SiteLogo />
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
39
frontend/src/components/Nav/SiteLogo.module.scss
Normal file
39
frontend/src/components/Nav/SiteLogo.module.scss
Normal file
@@ -0,0 +1,39 @@
|
||||
@keyframes blink {
|
||||
0% {
|
||||
transform: scaleY(1);
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
transform: scaleY(0.2);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scaleY(1);
|
||||
opacity: 1;
|
||||
}
|
||||
75% {
|
||||
transform: scaleY(0.2);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: scaleY(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.blinkingEyes :global(.pupilR),
|
||||
.blinkingEyes :global(.pupilL),
|
||||
.blinkingEyes :global(.eyeR),
|
||||
.blinkingEyes :global(.eyeL) {
|
||||
transform-origin: 0 7px;
|
||||
animation: blink 0.4s 2s ease;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
.blinkingEyes :global(.pupilR),
|
||||
.blinkingEyes :global(.pupilL),
|
||||
.blinkingEyes :global(.eyeR),
|
||||
.blinkingEyes :global(.eyeL) {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
20
frontend/src/components/Nav/SiteLogo.tsx
Normal file
20
frontend/src/components/Nav/SiteLogo.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import clsx from "clsx";
|
||||
import HolidayFrog from "../Frog/HolidayFrog";
|
||||
import styles from "./SiteLogo.module.scss";
|
||||
|
||||
export default function SiteLogo() {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"inline-flex items-center space-x-2",
|
||||
styles.blinkingEyes,
|
||||
)}
|
||||
aria-label="decomp.me logo"
|
||||
>
|
||||
<HolidayFrog />
|
||||
<span className="font-semibold text-xl leading-6 tracking-tight">
|
||||
decomp.me
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
|
||||
<title>decomp.me</title>
|
||||
<path fill="var(--frog-secondary)" d="M36 22c0 7.456-8.059 12-18 12S0 29.456 0 22 8.059 7 18 7s18 7.544 18 15z"/>
|
||||
<path fill="var(--frog-primary)" d="M31.755 12.676C33.123 11.576 34 9.891 34 8c0-3.313-2.687-6-6-6-2.861 0-5.25 2.004-5.851 4.685-1.288-.483-2.683-.758-4.149-.758-1.465 0-2.861.275-4.149.758C13.25 4.004 10.861 2 8 2 4.687 2 2 4.687 2 8c0 1.891.877 3.576 2.245 4.676C1.6 15.356 0 18.685 0 22c0 7.456 8.059 1 18 1s18 6.456 18-1c0-3.315-1.6-6.644-4.245-9.324z"/>
|
||||
<circle fill="#FFF" cx="7.5" cy="7.5" r="3.5" class="eyeL" />
|
||||
<circle fill="var(--frog-pupil)" cx="7.5" cy="7.5" r="1.5" class="pupilL" />
|
||||
<circle fill="#FFF" cx="28.5" cy="7.5" r="3.5" class="eyeR" />
|
||||
<circle fill="var(--frog-pupil)" cx="28.5" cy="7.5" r="1.5" class="pupilR" />
|
||||
<circle fill="var(--frog-nose)" cx="14" cy="20" r="1"/>
|
||||
<circle fill="var(--frog-nose)" cx="22" cy="20" r="1"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 995 B |
@@ -13,7 +13,7 @@ import { presetUrl, scratchUrl, userAvatarUrl } from "@/lib/api/urls";
|
||||
|
||||
import getTranslation from "@/lib/i18n/translate";
|
||||
|
||||
import AnonymousFrogAvatar from "./user/AnonymousFrog";
|
||||
import AnonymousFrogAvatar from "./Frog/AnonymousFrog";
|
||||
import PlatformLink from "./PlatformLink";
|
||||
import { calculateScorePercent, percentToString } from "./ScoreBadge";
|
||||
import styles from "./ScratchItem.module.scss";
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
.anonymousFrog {
|
||||
/* Default: decomp.me purple */
|
||||
--accent-hue: 298;
|
||||
--accent-saturation: 0.75;
|
||||
--accent-lightness: 0.4862;
|
||||
--frog-pupil: #292f33;
|
||||
--frog-primary:
|
||||
hsl(
|
||||
var(--accent-hue),
|
||||
calc(100% * var(--accent-saturation)),
|
||||
calc(100% * var(--accent-lightness))
|
||||
);
|
||||
|
||||
/* Pure CSS doesn't have anything quite as slick as Sass' color.scale [as used in theme.scss]
|
||||
for creating variations of a color. As such, we have to do a bit of the maths ourselves. */
|
||||
--frog-secondary:
|
||||
hsl(
|
||||
var(--accent-hue),
|
||||
calc(100% * (0.3 * (1 - var(--accent-saturation)) + var(--accent-saturation))), /* 30% towards maximum saturation */
|
||||
calc(100% * (0.5 * (1 - var(--accent-lightness)) + var(--accent-lightness))) /* 50% towards maximum lightness */
|
||||
);
|
||||
--frog-nose:
|
||||
hsl(
|
||||
calc(100% * (-0.2 * (var(--accent-saturation)) + var(--accent-saturation))), /* 20% towards minimum saturation */
|
||||
calc(100% * (-0.4 * (var(--accent-lightness)) + var(--accent-lightness))) /* 40% towards minimum lightness */
|
||||
);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import clsx from "clsx";
|
||||
import * as api from "@/lib/api";
|
||||
import { userAvatarUrl } from "@/lib/api/urls";
|
||||
|
||||
import AnonymousFrogAvatar from "./AnonymousFrog";
|
||||
import AnonymousFrogAvatar from "../Frog/AnonymousFrog";
|
||||
import styles from "./UserAvatar.module.scss";
|
||||
|
||||
export type Props = {
|
||||
|
||||
Reference in New Issue
Block a user