Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,27 @@ id: docs
title: Welcome to recode hive
sidebar_label: Welcome to recode hive
sidebar_position: 1
hide_title: true
description: "The story behind recode hive: why we started, how the community grew from one repo to 100+ contributors, and how to make your first contribution."
---

# Welcome to recode hive

**Collaboration 1st, code 2nd.**

recode hive is an open-source educational platform built to help developers master essential technologies through interactive tutorials, practical guides, and community-driven learning. Whether you are a beginner taking your first steps in programming or an experienced developer sharpening your skills, recode hive provides the resources you need to grow.
import {
StoryHero,
NameCards,
JourneyTimeline,
ImpactCounters,
FounderNote,
ContributorWall,
CtaBand,
} from '@site/src/components/docsStory';

<StoryHero />
<NameCards />
<JourneyTimeline />
<ImpactCounters />
<FounderNote />
<ContributorWall />
<CtaBand />

---

Expand Down
57 changes: 57 additions & 0 deletions src/components/docsStory/ContributorWall.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import { WALL_HANDLES } from './storyData';
import { useInView, useRunning } from './hooks';
import styles from './docsStory.module.css';

function Pill({ handle, hidden }: { handle: string; hidden?: boolean }) {
return (
<a
className={styles.pill}
href={`https://github.com/${handle}`}
target="_blank"
rel="noopener noreferrer"
tabIndex={hidden ? -1 : undefined}
aria-hidden={hidden || undefined}
>
<img
src={`https://github.com/${handle}.png?size=52`}
alt=""
width={26}
height={26}
loading="lazy"
className={styles.pillAvatar}
/>
<span>@{handle}</span>
{!hidden && <span className={styles.srOnly}> (opens in a new tab)</span>}
</a>
);
}

function Lane({ handles, reverse }: { handles: string[]; reverse?: boolean }) {
return (
<div className={styles.lane}>
<div className={`${styles.track} ${reverse ? styles.trackReverse : ''}`}>
{handles.map((h) => <Pill key={h} handle={h} />)}
{/* Duplicate set makes the loop seamless; hidden from assistive tech and the tab order */}
{handles.map((h) => <Pill key={`${h}-copy`} handle={h} hidden />)}
</div>
</div>
);
}

export default function ContributorWall() {
const [ref, inView] = useInView<HTMLElement>(0.1);
const running = useRunning(inView);
if (WALL_HANDLES.length === 0) return null;
const mid = Math.ceil(WALL_HANDLES.length / 2);
return (
<section ref={ref} className={`${styles.section} ${running ? styles.running : ''}`}>
<p className={styles.eyebrow}>{'✦'} THE HIVE</p>
<h2 className={styles.h2}>Built by these people, and you next</h2>
<div className={styles.wall}>
<Lane handles={WALL_HANDLES.slice(0, mid)} />
<Lane handles={WALL_HANDLES.slice(mid)} reverse />
</div>
</section>
);
}
22 changes: 22 additions & 0 deletions src/components/docsStory/CtaBand.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { GOOD_FIRST_ISSUES, SETUP_ANCHOR } from './storyData';
import styles from './docsStory.module.css';

export default function CtaBand() {
return (
<section className={styles.cta}>
<div>
<p className={styles.eyebrowCta}>{'✦'} YOUR TURN</p>
<h2 className={styles.ctaTitle}>Every hexagon in the hive started with one PR.</h2>
<p className={styles.ctaText}>Set up the repo in 10 minutes, then pick a good first issue.</p>
</div>
<div className={styles.ctaButtons}>
<a className={styles.btnGhost} href={SETUP_ANCHOR}>Set up locally {'↓'}</a>
<a className={styles.btnWhite} href={GOOD_FIRST_ISSUES} target="_blank" rel="noopener noreferrer">
Good first issues {'→'}
<span className={styles.srOnly}> (opens in a new tab)</span>
</a>
</div>
</section>
);
}
40 changes: 40 additions & 0 deletions src/components/docsStory/FounderNote.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { useState } from 'react';
import useBaseUrl from '@docusaurus/useBaseUrl';
import { FOUNDER } from './storyData';
import styles from './docsStory.module.css';

export default function FounderNote() {
const photo = useBaseUrl(FOUNDER.photo);
const [failed, setFailed] = useState(false);
// No quote written yet: hide the whole section rather than show a placeholder.
if (!FOUNDER.quote.trim()) return null;
const initials = FOUNDER.name.split(' ').map((w) => w[0]).join('').slice(0, 2);
return (
<section className={styles.section}>
<h2 className={styles.h2}>A note from the founder</h2>
<figure className={styles.founder}>
<span className={styles.quoteMark} aria-hidden="true">{'“'}</span>
{failed ? (
<span className={styles.founderPhoto} aria-hidden="true">{initials}</span>
) : (
<img
src={photo}
alt={FOUNDER.name}
width={96}
height={96}
loading="lazy"
className={styles.founderPhoto}
onError={() => setFailed(true)}
/>
)}
<div>
<blockquote className={styles.founderQuote}>{FOUNDER.quote}</blockquote>
<figcaption className={styles.founderName}>
<strong>{FOUNDER.name}</strong>
<span>{FOUNDER.role}</span>
</figcaption>
</div>
</figure>
</section>
);
}
30 changes: 30 additions & 0 deletions src/components/docsStory/HiveBee.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import styles from './docsStory.module.css';

/** The recode hive bee. The witch hat is added only when `hat` is true (Halloween window). */
export default function HiveBee({ hat }: { hat: boolean }) {
return (
<svg viewBox="0 0 80 80" width="58" height="58" focusable="false" aria-hidden="true">
<defs>
<clipPath id="rhStoryBee"><polygon points="40,30 59,41 59,63 40,74 21,63 21,41" /></clipPath>
</defs>
<g className={styles.wing}><ellipse cx="24" cy="36" rx="12" ry="8" transform="rotate(-25 24 36)" fill="rgba(226,232,240,.8)" stroke="#94a3b8" strokeWidth="1.5" /></g>
<g className={styles.wing}><ellipse cx="56" cy="36" rx="12" ry="8" transform="rotate(25 56 36)" fill="rgba(226,232,240,.8)" stroke="#94a3b8" strokeWidth="1.5" /></g>
<polygon points="40,30 59,41 59,63 40,74 21,63 21,41" fill="#fbbf24" stroke="#f59e0b" strokeWidth="2" strokeLinejoin="round" />
<g clipPath="url(#rhStoryBee)">
<rect x="18" y="56" width="44" height="5" fill="#0f172a" opacity=".85" />
<rect x="18" y="65" width="44" height="5" fill="#0f172a" opacity=".85" />
</g>
<circle cx="33.5" cy="45" r="5" fill="#fff" /><circle cx="46.5" cy="45" r="5" fill="#fff" />
<circle cx="34.5" cy="45.5" r="2.4" fill="#0f172a" /><circle cx="47.5" cy="45.5" r="2.4" fill="#0f172a" />
<path d="M36 51 Q40 54.5 44 51" fill="none" stroke="#0f172a" strokeWidth="1.6" strokeLinecap="round" />
{hat && (
<>
<ellipse cx="40" cy="31" rx="17" ry="3.6" fill="#6d28d9" />
<path d="M30 30 Q36 18 42 4 Q45 10 54 11 Q47 14 50 30 Z" fill="#7c3aed" />
<rect x="31" y="25" width="19" height="3.6" rx="1" fill="#f97316" />
</>
)}
</svg>
);
}
50 changes: 50 additions & 0 deletions src/components/docsStory/ImpactCounters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { useEffect, useState } from 'react';
import { IMPACT } from './storyData';
import { useInView, useReducedMotion } from './hooks';
import styles from './docsStory.module.css';

const fmt = (n: number, k: boolean) => {
if (k && n >= 1000) return `${(n / 1000).toFixed(2).replace(/0$/, '')}K`;
return Math.round(n).toLocaleString('en-US');
};

function Counter({ value, suffix, k, play }: { value: number; suffix: string; k: boolean; play: boolean }) {
const [shown, setShown] = useState(value); // final value for server render and reduced motion
useEffect(() => {
if (!play) return;
let raf = 0;
const start = performance.now();
const dur = 1200;
const tick = (now: number) => {
const t = Math.min(1, (now - start) / dur);
setShown(value * (1 - Math.pow(1 - t, 3)));
if (t < 1) raf = requestAnimationFrame(tick);
};
setShown(0);
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, [play, value]);
return <>{fmt(shown, k)}{suffix}</>;
}

export default function ImpactCounters() {
const [ref, seen] = useInView<HTMLElement>(0.4, true);
const reduced = useReducedMotion();
if (IMPACT.length === 0) return null;
return (
<section ref={ref} className={styles.section}>
<p className={styles.eyebrow}>{'✦'} IMPACT SO FAR</p>
<h2 className={styles.h2}>Small contributions, added up</h2>
<div className={styles.tiles}>
{IMPACT.map((t) => (
<div key={t.id} className={`${styles.tile} ${t.featured ? styles.tileFeatured : ''}`}>
<span className={styles.tileValue}>
<Counter value={t.value} suffix={t.suffix} k={t.k} play={seen && !reduced} />
</span>
<span className={styles.tileLabel}>{t.label}</span>
</div>
))}
</div>
</section>
);
}
Loading
Loading