Skip to content

Commit ead7785

Browse files
style: for hire badge
1 parent bb4fd68 commit ead7785

4 files changed

Lines changed: 226 additions & 1 deletion

File tree

consts.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
export const isProd =
2-
process.env.NODE_ENV === "production" && !process.env.TEST;
2+
process.env.NODE_ENV === 'production' && !process.env.TEST;
3+
4+
export const FOR_HIRE = true;
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
'use client';
2+
import React, { useState } from 'react';
3+
4+
const PortfolioIcon = () => (
5+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
6+
<rect x="2" y="7" width="20" height="14" rx="2" />
7+
<path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2" />
8+
</svg>
9+
);
10+
11+
const GitHubIcon = () => (
12+
<svg width="15" height="15" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
13+
<path fillRule="evenodd" d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z" clipRule="evenodd" />
14+
</svg>
15+
);
16+
17+
const LinkedInIcon = () => (
18+
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
19+
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
20+
</svg>
21+
);
22+
23+
export const ForHireBadge = () => {
24+
const [dismissed, setDismissed] = useState(false);
25+
26+
if (dismissed) return null;
27+
28+
return (
29+
<aside className="for-hire-badge" aria-label="Available for hire">
30+
<button
31+
className="for-hire-close"
32+
onClick={() => setDismissed(true)}
33+
aria-label="Close"
34+
>
35+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
36+
<line x1="1" y1="1" x2="11" y2="11" />
37+
<line x1="11" y1="1" x2="1" y2="11" />
38+
</svg>
39+
</button>
40+
41+
<div className="for-hire-status">
42+
<span className="for-hire-dot" aria-hidden="true" />
43+
<span className="for-hire-available">Available for hire</span>
44+
</div>
45+
46+
<nav className="for-hire-links" aria-label="Contact links">
47+
<a
48+
href="https://asmyshlyaev177.dev"
49+
target="_blank"
50+
rel="noopener noreferrer"
51+
className="for-hire-link"
52+
>
53+
<PortfolioIcon />
54+
<span>Portfolio</span>
55+
</a>
56+
<a
57+
href="https://github.com/asmyshlyaev177"
58+
target="_blank"
59+
rel="noopener noreferrer"
60+
className="for-hire-link"
61+
>
62+
<GitHubIcon />
63+
<span>GitHub</span>
64+
</a>
65+
<a
66+
href="https://www.linkedin.com/in/asmyshlyaev177/"
67+
target="_blank"
68+
rel="noopener noreferrer"
69+
className="for-hire-link"
70+
>
71+
<LinkedInIcon />
72+
<span>LinkedIn</span>
73+
</a>
74+
</nav>
75+
</aside>
76+
);
77+
};

packages/example-nextjs15/src/app/styles.css

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,3 +301,146 @@
301301
animation: none;
302302
}
303303
}
304+
305+
/* ── For Hire Badge ─────────────────────────────────────────── */
306+
307+
.for-hire-badge {
308+
position: fixed;
309+
bottom: 1.75rem;
310+
right: 1.75rem;
311+
z-index: 50;
312+
313+
background: white;
314+
border-radius: 1rem;
315+
padding: 0.875rem 1.125rem;
316+
min-width: 172px;
317+
318+
box-shadow:
319+
0 4px 24px oklch(0% 0 0 / 0.1),
320+
0 1px 4px oklch(0% 0 0 / 0.06),
321+
inset 0 0 0 1.5px oklch(87% 0.06 60 / 0.8);
322+
323+
border-top: 3px solid var(--brand);
324+
325+
animation: forHireSlideIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
326+
animation-delay: 0.3s;
327+
328+
@media (prefers-reduced-motion: reduce) {
329+
animation: none;
330+
}
331+
}
332+
333+
.for-hire-close {
334+
position: absolute;
335+
top: 0.5rem;
336+
right: 0.5rem;
337+
display: flex;
338+
align-items: center;
339+
justify-content: center;
340+
width: 20px;
341+
height: 20px;
342+
border-radius: 50%;
343+
border: none;
344+
background: transparent;
345+
color: var(--ink-2);
346+
cursor: pointer;
347+
padding: 0;
348+
transition:
349+
color 0.15s ease,
350+
background-color 0.15s ease;
351+
352+
&:hover {
353+
color: var(--ink);
354+
background-color: oklch(92% 0.01 260 / 0.6);
355+
}
356+
}
357+
358+
.for-hire-status {
359+
display: flex;
360+
align-items: center;
361+
gap: 0.5rem;
362+
margin-bottom: 0.75rem;
363+
padding-right: 1.25rem;
364+
}
365+
366+
.for-hire-dot {
367+
display: inline-block;
368+
width: 8px;
369+
height: 8px;
370+
border-radius: 50%;
371+
background: oklch(65% 0.2 145);
372+
box-shadow: 0 0 0 0 oklch(65% 0.2 145 / 0.6);
373+
flex-shrink: 0;
374+
animation: forHirePulse 2.4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
375+
376+
@media (prefers-reduced-motion: reduce) {
377+
animation: none;
378+
}
379+
}
380+
381+
.for-hire-available {
382+
font-family: var(--font-syne), system-ui, sans-serif;
383+
font-size: 0.72rem;
384+
font-weight: 700;
385+
letter-spacing: 0.1em;
386+
text-transform: uppercase;
387+
color: var(--brand);
388+
}
389+
390+
.for-hire-links {
391+
display: flex;
392+
flex-direction: column;
393+
gap: 0.45rem;
394+
}
395+
396+
.for-hire-link {
397+
display: inline-flex;
398+
align-items: center;
399+
gap: 0.5rem;
400+
font-size: 0.825rem;
401+
font-weight: 500;
402+
color: var(--ink-2);
403+
text-decoration: none;
404+
border-radius: 0.35rem;
405+
padding: 0.2rem 0.3rem;
406+
margin: -0.2rem -0.3rem;
407+
transition:
408+
color 0.18s ease,
409+
background-color 0.18s ease;
410+
411+
&:hover {
412+
color: var(--brand);
413+
background-color: oklch(92% 0.06 60 / 0.5);
414+
}
415+
416+
& svg {
417+
flex-shrink: 0;
418+
opacity: 0.75;
419+
transition: opacity 0.18s ease;
420+
}
421+
422+
&:hover svg {
423+
opacity: 1;
424+
}
425+
}
426+
427+
@keyframes forHirePulse {
428+
0%,
429+
100% {
430+
box-shadow: 0 0 0 0 oklch(65% 0.2 145 / 0.6);
431+
}
432+
50% {
433+
box-shadow: 0 0 0 6px oklch(65% 0.2 145 / 0);
434+
}
435+
}
436+
437+
@keyframes forHireSlideIn {
438+
from {
439+
opacity: 0;
440+
transform: translateY(16px) translateX(8px);
441+
}
442+
to {
443+
opacity: 1;
444+
transform: translateY(0) translateX(0);
445+
}
446+
}

packages/example-nextjs15/src/app/template.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import React from 'react';
44
import './styles.css';
55

66
import { Logo } from './components/Logo';
7+
import { FOR_HIRE } from '../../../../consts';
8+
import { ForHireBadge } from './components/ForHireBadge';
79

810
const Footer = dynamic(
911
() => import('./components/Footer').then((mod) => mod.Footer),
@@ -36,6 +38,7 @@ export default async function Template({
3638
</div>
3739

3840
<Footer />
41+
{FOR_HIRE && <ForHireBadge />}
3942
</main>
4043
);
4144
}

0 commit comments

Comments
 (0)