on master help sign in
add assembly page, support multiday events (#1094)

* add assembly page, support multiday events * adds banner * resolves visual bugs * use TwoUp universally * minor footer top margin

commitc79b4f4d44ca8e853e3a4e67abdb4fb6f9dfb71f authorGavin <5265021+g-a-v-i-n@users.noreply.github.com> dateFri 3 Sep 2021, 14:37 (5 years ago) parenta68427a
19 files changed+340−208
components/Contact.js+1−1 components/EventPreview.js+17−12 components/Footer.js+1−1 components/Section.js+2−2 components/Snippets.js+44 components/TwoUp.js+12 components/icons/Cross.js+11 content/events/2021-10-15-assembly.md+26 lib/constants.js+55 lib/hooks.js+22 next.config.js+1 package-lock.json+5 package.json+2−1 pages/blog/[slug].js+9−18 pages/events.js+5−19 pages/events/[slug].js+37−90 pages/index.js+66−37 styles/globals.css+22−25 styles/markdown.css+2−2
components/Contact.js+1−1
@@ -85,7 +85,7 @@ export default function Contact({ emphasize }) {
8585 Discord
8686 </a>
8787 </h4>
88− <h4 className="mt-6 text-wall-500 flex items-baseline flex-wrap">
88+ <h4 className="mt-4 text-wall-500 flex items-baseline flex-wrap">
8989 Boot Urbit and join{" "}
9090 <div className="md:ml-2 rounded-lg flex items-stretch">
9191 <code
components/EventPreview.js+17−12
@@ -1,6 +1,11 @@
11import Link from "next/link";
22import { DateTime } from "luxon";
3−import { ShowOrHide, ReadableList, Person } from "../components/Snippets";
3+import {
4+ ShowOrHide,
5+ ReadableList,
6+ Person,
7+ DateRange,
8+} from "../components/Snippets";
49import {
510 generateDisplayDate,
611 generateRealtimeDate,
@@ -11,7 +16,7 @@ import {
1116
1217export default function EventPreview({ event, className, big }) {
1318 // Event tiles have a 'dark mode' used when their background images are dark and white text is needed for legibility.
14− const grayText = event?.dark ? "text-midWhite" : "text-wall-400";
19+ const grayText = event?.dark ? "text-washedWhite" : "text-wall-400";
1520 const blackText = event?.dark ? "text-white" : "text-wall-600";
1621
1722 const starts = generateDisplayDate(event.starts, event.timezone);
@@ -23,7 +28,9 @@ export default function EventPreview({ event, className, big }) {
2328 generateRealtimeDate(event.starts) > DateTime.now() && !inFuture;
2429
2530 return (
26− <div className={`cursor-pointer aspect-w-5 aspect-h-4 ${className}`}>
31+ <div
32+ className={`cursor-pointer aspect-w-4 aspect-h-5 md:aspect-w-5 md:aspect-h-4 ${className}`}
33+ >
2734 <div
2835 key={event.slug}
2936 className={`bg-wall-100 rounded-xl bg-cover bg-center bg-no-repeat `}
@@ -38,15 +45,13 @@ export default function EventPreview({ event, className, big }) {
3845 big ? "justify-center" : ""
3946 } flex-col h-full`}
4047 >
41− <h4 className={`${blackText}`}>{event.title}</h4>
42− <p className={grayText}>{formatDate(starts)}</p>
43− <p className={grayText}>
44− {formatTime(starts)}
45− {typeof event.ends !== "undefined"
46− ? ` to ${formatTime(ends)}`
47− : null}
48− {" " + formatTimeZone(starts)}
49− </p>
48+ <h3 className={`${blackText} mb-2`}>{event.title}</h3>
49+ <p className={`${blackText} mb-2`}>{event.location}</p>
50+ <DateRange
51+ starts={starts}
52+ ends={ends}
53+ className={`${grayText} type-sub`}
54+ />
5055 </div>
5156
5257 <div className="absolute p-6 left-0 bottom-0 w-full pr-32">
components/Footer.js+1−1
@@ -5,7 +5,7 @@ import { contact } from "../lib/constants";
55
66export default function Footer() {
77 return (
8− <footer className="bg-wall-100 w-full mt-48 flex justify-center">
8+ <footer className="bg-wall-100 mt-20 w-full flex justify-center">
99 <SingleColumn>
1010 <Section short className="flex flex-row flex-wrap">
1111 <div className="w-1/2 md:w-1/3 flex flex-col flex-shrink">
components/Section.js+2−2
@@ -8,8 +8,8 @@ export default function Section({
88 narrow = false,
99}) {
1010 const spacing = classnames({
11− "py-4 md:py-6 lg:py-8": short,
12− "py-8 md:py-10 lg:py-20": !short,
11+ "py-8": short,
12+ "py-20": !short,
1313 "layout-narrow": narrow,
1414 layout: !narrow,
1515 });
components/Snippets.js+44
@@ -1,3 +1,12 @@
1+import {
2+ getAllEvents,
3+ generateDisplayDate,
4+ generateRealtimeDate,
5+ formatDate,
6+ formatTime,
7+ formatTimeZone,
8+} from "../lib/lib";
9+
110export function Name({ children, className }) {
211 return <b className={`font-normal ${className || ""}`}>{children}</b>;
312}
@@ -61,3 +70,38 @@ export function ShowOrHide({ children, condition }) {
6170 }
6271 return null;
6372}
73+
74+export function DateRange({ starts, ends, className, short }) {
75+ // For events which have no end datetime
76+ if (!ends.isValid) {
77+ return (
78+ <div>
79+ <p className={className}>
80+ {`${formatDate(starts)} • ${formatTime(starts)} ${formatTimeZone(
81+ starts
82+ )}`}
83+ </p>
84+ </div>
85+ );
86+ }
87+ // For events which start and end on the same day
88+ if (starts.hasSame(ends, "day")) {
89+ return (
90+ <div>
91+ <p className={className}>
92+ {`${formatDate(starts)} • ${formatTime(starts)} to ${formatTime(
93+ ends
94+ )} ${formatTimeZone(starts)}`}
95+ </p>
96+ </div>
97+ );
98+ }
99+ // For multi-day events
100+ return (
101+ <div>
102+ <p className={className}>{`${starts.toFormat(
103+ "cccc, LLLL d"
104+ )} to ${formatDate(ends)}`}</p>
105+ </div>
106+ );
107+}
components/TwoUp.jsnew file+12
@@ -0,0 +1,12 @@
1+export default function TwoUp({ children, className = "" }) {
2+ return (
3+ <div className={`flex flex-wrap w-full ${className}`}>
4+ <div className="w-full md:w-1/2 pr-0 pb-8 md:pr-4">
5+ {children[0] || null}
6+ </div>
7+ <div className="w-full md:w-1/2 pl-0 pb-8 md:pl-4">
8+ {children[1] || null}
9+ </div>
10+ </div>
11+ );
12+}
components/icons/Cross.jsnew file+11
@@ -0,0 +1,11 @@
1+export default function Cross(props) {
2+ return (
3+ <svg {...props} viewBox="0 0 12 12">
4+ <path
5+ fillRule="evenodd"
6+ clipRule="evenodd"
7+ d="M0.292893 10.2942C-0.0976311 10.6847 -0.0976311 11.3179 0.292893 11.7084C0.683417 12.0989 1.31658 12.0989 1.70711 11.7084L6.00063 7.41487L10.2948 11.7091C10.6853 12.0996 11.3185 12.0996 11.709 11.7091C12.0996 11.3185 12.0996 10.6854 11.709 10.2949L7.41484 6.00066L11.7084 1.70711C12.0989 1.31658 12.0989 0.683417 11.7084 0.292894C11.3179 -0.0976312 10.6847 -0.0976312 10.2942 0.292894L6.00063 4.58645L1.70775 0.293571C1.31723 -0.0969534 0.684061 -0.0969534 0.293536 0.293571C-0.0969879 0.684095 -0.0969879 1.31726 0.293536 1.70778L4.58641 6.00066L0.292893 10.2942Z"
8+ />
9+ </svg>
10+ );
11+}
content/events/2021-10-15-assembly.mdnew file+26
@@ -0,0 +1,26 @@
1++++
2+title = "Assembly"
3+starts = "2021-10-15T15:30:00"
4+ends = "2021-10-17T18:00:00"
5+timezone = "America/Chicago"
6+location = "Distribution Hall, 1500 East 4th St. Austin, TX"
7+image = "https://storage.googleapis.com/media.urbit.org/site/events/assembly-image.png"
8+registration_url = "http://assembly.urbit.org/"
9+description = "Grow a new internet"
10+dark = true
11++++
12+
13+![](https://storage.googleapis.com/media.urbit.org/site/events/assembly-event-image.png)
14+
15+Assembly 2021 marks a pivotal moment in Urbit: software distribution. Yes, Urbit is ready to be built upon. We’ll spend three days together doing just that.
16+
17+- Learn about building applications on Urbit (it’s easy).
18+- Find out how to work and create on a platform that is fully owned by you.
19+- Meet with the growing ecosystem of communities and businesses built on Urbit.
20+
21+If you’re simply into calm computing, well, there will be plenty of that too.
22+
23+You’ll choose between two tracks:
24+
25+- Assembly: Assemble software or work together on projects in small groups
26+- Workshop: A two day workshop taught by Neal Davis, Urbit for Developers
lib/constants.js+55
@@ -8,3 +8,58 @@ export const contact = {
88 newsletter:
99 "https://urbit.us11.list-manage.com/subscribe/post?u=972a03db9e0c6c25bb58de8c8&amp;id=be143888d2",
1010};
11+
12+export const eventKeys = [
13+ "title",
14+ "ends",
15+ "location",
16+ "image",
17+ "registration_url",
18+ "youtube",
19+ "description",
20+ "starts",
21+ "hosts",
22+ "slug",
23+ "guests",
24+ "content",
25+ "dark",
26+ "timezone",
27+];
28+
29+export const postKeys = [
30+ "title",
31+ "slug",
32+ "date",
33+ "description",
34+ "content",
35+ "extra",
36+];
37+
38+export const grantsKeys = [
39+ "title",
40+ "slug",
41+ "date",
42+ "description",
43+ "content",
44+ "extra",
45+ "taxonomies",
46+];
47+
48+export const mediaKeys = [
49+ "title",
50+ "slug",
51+ "date",
52+ "description",
53+ "content",
54+ "extra",
55+];
56+
57+export const updatesKeys = [
58+ "title",
59+ "slug",
60+ "date",
61+ "description",
62+ "content",
63+ "author",
64+ "ship",
65+];
lib/hooks.js+22
@@ -35,3 +35,25 @@ export function useCopyToClipboard(text, notifyTimeout = 1000) {
3535
3636 return [copyStatus, copy];
3737}
38+
39+function getStorageValue(key, defaultValue) {
40+ // getting stored value
41+ if (typeof window !== "undefined") {
42+ const saved = window.localStorage.getItem(key);
43+ const initial = saved !== null ? JSON.parse(saved) : defaultValue;
44+ return initial;
45+ }
46+}
47+
48+export const useLocalStorage = (key, defaultValue) => {
49+ const [value, setValue] = useState(() => {
50+ return getStorageValue(key, defaultValue);
51+ });
52+
53+ useEffect(() => {
54+ // storing input name
55+ window.localStorage.setItem(key, JSON.stringify(value));
56+ }, [key, value]);
57+
58+ return [value, setValue];
59+};
next.config.js+1
@@ -1,4 +1,5 @@
11module.exports = {
2+ reactStrictMode: false,
23 // target: 'serverless',
34 webpack: (config, { isServer }) => {
45 // Fixes npm packages that depend on `fs` module
package-lock.json+5
@@ -5748,6 +5748,11 @@
57485748 "resolved": "https://registry.npmjs.org/react-swipeable/-/react-swipeable-6.1.2.tgz",
57495749 "integrity": "sha512-vfZtOZNivwd/aI+ZZH1Grx0eQBdbV1UI3pB9p65jbW5guHHdSIPpKsND6XmaiZXP5REOOc9Ckfr36ChswPqwsA=="
57505750 },
5751+ "react-use-localstorage": {
5752+ "version": "3.5.3",
5753+ "resolved": "https://registry.npmjs.org/react-use-localstorage/-/react-use-localstorage-3.5.3.tgz",
5754+ "integrity": "sha512-1oNvJmo72G4v5P9ytJZZTb6ywD3UzWBiainTtfbNlb+U08hc+SOD5HqgiLTKUF0MxGcIR9JSnZGmBttNLXaQYA=="
5755+ },
57515756 "readable-stream": {
57525757 "version": "2.3.7",
57535758 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
package.json+2−1
@@ -24,6 +24,7 @@
2424 "fuzzy-search": "^3.2.1",
2525 "gray-matter": "^4.0.3",
2626 "html-entities": "^2.3.2",
27+ "luxon": "^2.0.2",
2728 "next": "10.2.3",
2829 "patch-package": "^6.4.7",
2930 "postcss": "^8.3.0",
@@ -31,6 +32,7 @@
3132 "react-dom": "17.0.2",
3233 "react-hotkeys": "^2.0.0",
3334 "react-swipeable": "^6.1.2",
35+ "react-use-localstorage": "^3.5.3",
3436 "remark": "^13.0.0",
3537 "remark-gfm": "^1.0.0",
3638 "remark-heading-id": "^1.0.0",
@@ -39,7 +41,6 @@
3941 "remark-slug": "^6.0.0",
4042 "tailwindcss": "^2.1.4",
4143 "toml": "^3.0.0",
42− "luxon": "^2.0.2",
4344 "unified": "^9.2.1"
4445 },
4546 "devDependencies": {
pages/blog/[slug].js+9−18
@@ -18,6 +18,7 @@ import SingleColumn from "../../components/SingleColumn";
1818import Section from "../../components/Section";
1919import Contact from "../../components/Contact";
2020import PostPreview from "../../components/PostPreview";
21+import TwoUp from "../../components/TwoUp";
2122import { decode } from "html-entities";
2223
2324export default function Post({
@@ -64,24 +65,14 @@ export default function Post({
6465 <Contact />
6566 </Section>
6667 <Section wide className="flex">
67− {previousPost === null ? (
68− <div className={"w-1/2 mr-4"} />
69− ) : (
70− <PostPreview
71− title="Previous Post"
72− post={previousPost}
73− className="mr-4 w-1/2"
74− />
75− )}
76− {nextPost === null ? (
77− <div className={"w-1/2 ml-4"} />
78− ) : (
79− <PostPreview
80− title="Next Post"
81− post={nextPost}
82− className="ml-4 w-1/2"
83− />
84− )}
68+ <TwoUp>
69+ {nextPost ? (
70+ <PostPreview title="Next Post" post={nextPost} />
71+ ) : null}
72+ {previousPost ? (
73+ <PostPreview title="Previous Post" post={previousPost} />
74+ ) : null}
75+ </TwoUp>
8576 </Section>
8677 </SingleColumn>
8778 <Footer />
pages/events.js+5−19
@@ -19,6 +19,8 @@ import {
1919 formatTime,
2020} from "../lib/lib";
2121
22+import { eventKeys } from "../lib/constants";
23+
2224export default function Events({ events, search }) {
2325 const post = {
2426 title: "Events",
@@ -71,7 +73,7 @@ export default function Events({ events, search }) {
7173 <h3 className="text-green-400">Happening Now</h3>
7274 </div>
7375 {happeningNow.map((post) => {
74− return <EventPreview big event={post} />;
76+ return <EventPreview big event={post} className="mb-8" />;
7577 })}
7678 </>
7779 ) : null}
@@ -81,7 +83,7 @@ export default function Events({ events, search }) {
8183 <h3 className="text-green-400">Coming Soon</h3>
8284 </div>
8385 {futureEvents.map((post) => {
84− return <EventPreview big event={post} />;
86+ return <EventPreview big event={post} className="mb-8" />;
8587 })}
8688 </>
8789 ) : null}
@@ -100,23 +102,7 @@ export default function Events({ events, search }) {
100102}
101103
102104export async function getStaticProps() {
103− const events = getAllEvents(
104− [
105− "title",
106− "ends",
107− "location",
108− "image",
109− "registration_url",
110− "youtube",
111− "description",
112− "starts",
113− "hosts",
114− "guests",
115− "dark",
116− "slug",
117− ],
118− "events"
119− );
105+ const events = getAllEvents(eventKeys, "events");
120106
121107 return {
122108 props: { events },
pages/events/[slug].js+37−90
@@ -21,8 +21,16 @@ import SingleColumn from "../../components/SingleColumn";
2121import Contact from "../../components/Contact";
2222import EventPreview from "../../components/EventPreview";
2323import Section from "../../components/Section";
24−import { Person, ReadableList, ShowOrHide } from "../../components/Snippets";
24+import TwoUp from "../../components/TwoUp";
25+
26+import {
27+ Person,
28+ ReadableList,
29+ ShowOrHide,
30+ DateRange,
31+} from "../../components/Snippets";
2532import { decode } from "html-entities";
33+import { eventKeys } from "../../lib/constants";
2634
2735export default function Event({
2836 event,
@@ -50,17 +58,11 @@ export default function Event({
5058 <Section narrow short>
5159 <h1>{event.title}</h1>
5260 <h3 className="mt-6">{event.description}</h3>
53− <p className="text-wall-600 mt-6">{formatDate(starts)}</p>
54− <p className="text-wall-600">
55− {formatTime(starts)}
56− <ShowOrHide condition={typeof event.ends !== "undefined"}>
57− {` to ${formatTime(ends)}`}
58− {" " + formatTimeZone(ends)}
59− </ShowOrHide>
60− </p>
61− <div className="mt-6">
61+ <p className="mt-6">{event.location}</p>
62+ <DateRange starts={starts} ends={ends} className="text-wall-400" />
63+ <div>
6264 <ShowOrHide condition={event.hosts}>
63− <p>
65+ <p className="mt-6">
6466 {"Hosted by "}
6567 <ReadableList>
6668 {event.hosts?.map((host, index) => {
@@ -103,8 +105,9 @@ export default function Event({
103105 </div>
104106 ) : null}
105107 </Section>
106− <Section short wide>
107− {event.youtube ? (
108+
109+ {event.youtube ? (
110+ <Section short wide>
108111 <iframe
109112 className="rounded-xl"
110113 width="100%"
@@ -114,8 +117,9 @@ export default function Event({
114117 allow="encrypted-media"
115118 allowFullScreen
116119 ></iframe>
117− ) : null}
118− </Section>
120+ </Section>
121+ ) : null}
122+
119123 <Section short narrow className="markdown">
120124 <article
121125 className="pt-12 w-full"
@@ -126,22 +130,21 @@ export default function Event({
126130 <Contact />
127131 </Section>
128132 <Section wide className="flex">
129− {previousEvent === null ? (
130− <div className={"w-1/2 mr-4"} />
131− ) : (
132− <div className="mr-4 w-1/2">
133− <h3 className="mb-2">Next Event</h3>
134− <EventPreview event={previousEvent} />
135− </div>
136− )}
137− {nextEvent === null ? (
138− <div className={"w-1/2 ml-4"} />
139− ) : (
140− <div className="mr-4 w-1/2">
141− <h3 className="mb-2">Previous Event</h3>
142− <EventPreview event={nextEvent} />
143− </div>
144− )}
133+ <TwoUp>
134+ {previousEvent ? (
135+ <div>
136+ <h3 className="mb-2">Next Event</h3>
137+ <EventPreview event={previousEvent} />
138+ </div>
139+ ) : null}
140+
141+ {nextEvent ? (
142+ <div>
143+ <h3 className="mb-2">Previous Event</h3>
144+ <EventPreview event={nextEvent} />
145+ </div>
146+ ) : null}
147+ </TwoUp>
145148 </Section>
146149 </SingleColumn>
147150 <Footer />
@@ -151,68 +154,12 @@ export default function Event({
151154
152155//
153156export async function getStaticProps({ params }) {
154− const nextEvent =
155− getNextPost(
156− params.slug,
157− [
158− "title",
159− "slug",
160− "ends",
161− "location",
162− "image",
163− "registration_url",
164− "youtube",
165− "description",
166− "starts",
167− "hosts",
168− "guests",
169− "dark",
170− "timezone",
171− ],
172− "events"
173− ) || null;
157+ const nextEvent = getNextPost(params.slug, eventKeys, "events") || null;
174158
175159 const previousEvent =
176− getPreviousPost(
177− params.slug,
178− [
179− "title",
180− "slug",
181− "ends",
182− "location",
183− "image",
184− "registration_url",
185− "youtube",
186− "description",
187− "starts",
188− "hosts",
189− "guests",
190− "dark",
191− "timezone",
192− ],
193− "events"
194− ) || null;
160+ getPreviousPost(params.slug, eventKeys, "events") || null;
195161
196− const event = getPostBySlug(
197− params.slug,
198− [
199− "title",
200− "ends",
201− "location",
202− "image",
203− "registration_url",
204− "youtube",
205− "description",
206− "starts",
207− "hosts",
208− "slug",
209− "guests",
210− "content",
211− "dark",
212− "timezone",
213− ],
214− "events"
215− );
162+ const event = getPostBySlug(params.slug, eventKeys, "events");
216163
217164 const markdown = await Markdown({ post: event });
218165
pages/index.js+66−37
@@ -1,29 +1,58 @@
11import Head from "next/head";
22import Link from "next/link";
33import React, { useState, useEffect } from "react";
4+
45import Container from "../components/Container";
56import Section from "../components/Section";
67import SingleColumn from "../components/SingleColumn";
78import Header from "../components/Header";
89import Footer from "../components/Footer";
910import BackgroundImage from "../components/BackgroundImage";
10−import TabCarousel from "../components/TabCarousel";
1111import Contact from "../components/Contact";
1212import PostPreview from "../components/PostPreview";
1313import EventPreview from "../components/EventPreview";
14+import Cross from "../components/icons/Cross";
15+import TwoUp from "../components/TwoUp";
1416import {
1517 getAllPosts,
1618 getAllEvents,
1719 formatDate,
1820 getOpenGrantsCount,
1921} from "../lib/lib";
20−import { contact } from "../lib/constants";
22+import { contact, eventKeys } from "../lib/constants";
23+import { useLocalStorage } from "../lib/hooks";
24+
25+const Banner = ({ children, isOpen, href, dismiss }) => {
26+ return (
27+ <div className="w-full flex justify-center bg-green-100">
28+ <SingleColumn>
29+ <div className="w-full layout">
30+ <div className="w-full flex justify-between items-center px-4 md:px-8 py-4">
31+ <a href={href} target="_blank">
32+ {children}
33+ </a>
34+ <button
35+ className="type-ui w-6 h-6 bg-green-400 flex items-center justify-center rounded-full text-white hover:opacity-70"
36+ onClick={(e) => {
37+ e.stopPropagation();
38+ dismiss();
39+ }}
40+ >
41+ <Cross width="10" height="10" fill="#E5F7F1" />
42+ </button>
43+ </div>
44+ </div>
45+ </SingleColumn>
46+ </div>
47+ );
48+};
2149
2250export default function Home({ posts, events, openGrantsCount, search }) {
23− const [tab, setTab] = useState(0);
2451 const [heroButton, setHeroButton] = useState(<div />);
52+ const [bannerElement, setBannerElement] = useState(null);
53+ const [isBannerOpen, setBanner] = useLocalStorage("isBannerOpen", true);
2554
26− const detectOS = () => {
55+ const selectDownloadButton = () => {
2756 const agent = window.navigator.appVersion;
2857 if (agent.includes("Win")) {
2958 return (
@@ -49,17 +78,37 @@ export default function Home({ posts, events, openGrantsCount, search }) {
4978 };
5079
5180 useEffect(() => {
52− setHeroButton(detectOS());
81+ setHeroButton(selectDownloadButton());
5382 }, []);
5483
84+ // Use this pattern when depending on client-side state or conditions like localStorage, user OS or geolocation.
85+ const selectBanner = (isBannerOpen) => {
86+ if (isBannerOpen) {
87+ return (
88+ <Banner
89+ href="http://assembly.urbit.org/"
90+ dismiss={() => setBanner(false)}
91+ >
92+ <p className="text-green-400 font-semibold hover:opacity-70">{`-> Join us October 15-17 for Assembly`}</p>
93+ </Banner>
94+ );
95+ } else {
96+ return null;
97+ }
98+ };
99+
100+ useEffect(() => {
101+ setBannerElement(selectBanner(isBannerOpen));
102+ }, [isBannerOpen]);
103+
55104 return (
56105 <Container>
57106 <Head>
58107 <title>urbit.org</title>
59108 </Head>
109+ {bannerElement}
60110 <SingleColumn>
61111 <Header search={search} />
62−
63112 {
64113 // Hero Statement
65114 }
@@ -197,14 +246,11 @@ export default function Home({ posts, events, openGrantsCount, search }) {
197246 <h2>Blog</h2>
198247 </div>
199248
200− <div className="flex flex-wrap">
201− <div className="w-full md:w-1/2 pr-0 pb-8 md:pr-4">
202− <PostPreview post={posts[0]} key={posts[0].slug} />
203− </div>
204− <div className="w-full md:w-1/2 pl-0 pb-8 md:pl-4">
205− <PostPreview post={posts[1]} key={posts[1].slug} />
206− </div>
207− </div>
249+ <TwoUp>
250+ <PostPreview post={posts[0]} key={posts[0].slug} />
251+ <PostPreview post={posts[1]} key={posts[1].slug} />
252+ </TwoUp>
253+
208254 <Link href="/blog">
209255 <button className="button-lg type-ui text-white bg-green-400">
210256 See More
@@ -220,14 +266,11 @@ export default function Home({ posts, events, openGrantsCount, search }) {
220266 <h2>Events</h2>
221267 </div>
222268
223− <div className="flex flex-wrap">
224− <div className="w-full md:w-1/2 pr-0 pb-8 md:pr-4">
225− <EventPreview event={events[0]} key={events[0].slug} />
226− </div>
227− <div className="w-full md:w-1/2 pl-0 pb-8 md:pl-4">
228− <EventPreview event={events[1]} key={events[1].slug} />
229− </div>
230− </div>
269+ <TwoUp>
270+ <EventPreview event={events[0]} key={events[0].slug} />
271+ <EventPreview event={events[1]} key={events[1].slug} />
272+ </TwoUp>
273+
231274 <Link href="/events">
232275 <button className="button-lg type-ui text-white bg-wall-600">
233276 More Events
@@ -256,21 +299,7 @@ export async function getStaticProps() {
256299 "blog"
257300 );
258301
259− const events = getAllEvents(
260− [
261− "title",
262− "slug",
263− "starts",
264− "guests",
265− "hosts",
266− "registration_url",
267− "ends",
268− "timezone",
269− "youtube",
270− "image",
271− ],
272− "events"
273− );
302+ const events = getAllEvents(eventKeys, "events");
274303
275304 return {
276305 props: { posts, events, openGrantsCount },
styles/globals.css+22−25
@@ -1,5 +1,4 @@
1−@import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;500;600;700&display=swap");
2−@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Source+Code+Pro:wght@400;500;600;700&display=swap");
1+@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Source+Code+Pro:wght@400;500;600;700;800&display=swap");
32
43:root {
54 --tray-time: 0ms;
@@ -9,69 +8,67 @@ html,
98body {
109 padding: 0;
1110 margin: 0;
11+ /* Make all text wall-600 by default */
1212 @apply text-wall-600;
1313}
1414
15+/* Reset a tags */
1516a {
1617 color: inherit;
1718 text-decoration: none;
1819 cursor: pointer;
1920}
2021
21−b,
22−strong {
23− @apply font-semibold;
24−}
25−
22+/* Links in p tags should be green */
2623p a {
2724 @apply text-green-400 font-semibold;
2825}
2926
30−button {
31− border: none;
27+/* Reduce b and strong tag weight default */
28+b,
29+strong {
30+ @apply font-semibold;
3231}
3332
3433/* Typography */
3534
3635h1,
3736.type-h1 {
38− @apply text-3xl md:text-4xl lg:text-5xl font-bold leading-tight text-wall-600;
37+ @apply text-4xl lg:text-5xl font-extrabold leading-tight text-wall-600;
3938}
4039
4140h2,
4241.type-h2 {
43− @apply text-2xl md:text-3xl lg:text-4xl font-bold leading-tight;
42+ @apply text-3xl lg:text-4xl font-bold leading-tight;
4443}
4544
4645h3,
4746.type-h3 {
48− @apply text-lg md:text-xl lg:text-2xl font-semibold;
47+ @apply text-xl font-semibold;
4948}
5049
5150h4,
5251.type-ui {
53− @apply text-sm md:text-base lg:text-lg font-semibold;
52+ @apply text-lg font-semibold;
5453}
5554
56−.type-ui-small {
57− @apply text-xs lg:text-base font-medium;
55+h5,
56+.type-h5,
57+.type-bold {
58+ @apply text-base font-semibold leading-relaxed;
5859}
5960
6061p,
6162.type-p {
62− @apply text-sm md:text-base lg:text-base leading-relaxed;
63−}
64−
65−.type-bold {
66− @apply text-sm md:text-base lg:text-base font-semibold leading-relaxed;
63+ @apply text-base leading-relaxed;
6764}
6865
6966.type-sub {
70− @apply text-xs md:text-sm;
67+ @apply text-sm;
7168}
7269
7370.type-sub-bold {
74− @apply text-xs md:text-sm font-semibold;
71+ @apply text-sm font-semibold;
7572}
7673
7774code .type-code {
@@ -109,15 +106,15 @@ code .type-code {
109106}
110107
111108.button-lg {
112− @apply flex-center px-4 lg:px-6 py-1 rounded-xl h-10 md:h-14 type-ui;
109+ @apply flex-center px-6 py-1 rounded-xl h-14 type-ui;
113110}
114111
115112.badge-sm {
116− @apply flex-center type-ui-small px-4 lg:px-4 h-8 md:h-8 rounded-full;
113+ @apply flex-center text-base font-medium px-3 h-8 rounded-full;
117114}
118115
119116.badge-lg {
120− @apply flex-center type-ui px-4 lg:px-8 h-10 md:h-12 rounded-full;
117+ @apply flex-center type-ui px-5 h-12 rounded-full;
121118}
122119
123120.button-sm:hover,
styles/markdown.css+2−2
@@ -36,11 +36,11 @@
3636}
3737
3838.markdown p {
39− @apply text-sm md:text-lg leading-relaxed;
39+ @apply text-base md:text-lg leading-relaxed;
4040}
4141
4242.markdown li {
43− @apply py-2 ml-6 text-lg;
43+ @apply py-2 ml-6 text-base md:text-lg;
4444}
4545
4646.markdown hr {