From 20a19850becbd7b6a362d1a96a71e5b21de4f2c8 Mon Sep 17 00:00:00 2001 From: Sinduri Guntupalli Date: Fri, 5 Jun 2026 09:56:22 +0200 Subject: [PATCH 1/7] perf: add decoding=async to lazy images, fix architecture eager load, preload hero bg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `decoding="async"` to all lazy-loaded images (AvatarLink, Footer, BoardSection, BottomCTA, DiscussionSection, Sponsors) so image decoding happens off the main thread - Change ArchitectureSection diagram from `loading="eager"` to `loading="lazy" decoding="async"` — the diagram is below the fold on challenge detail pages - Add `links()` export to About.tsx with `rel="preload"` for nyx_peek.webp, which is the above-fold hero background image that the browser cannot discover until after CSS layout - Add `aria-labelledby` + matching `id` to Hero, PageHero, and ChallengesGrid sections for improved landmark navigation - Update CLAUDE.md, PERFORMANCE.md, README.md, styleguide.md, ADVENTURES.md, CONTRIBUTING.md with doc fixes from prior work Signed-off-by: Sinduri Guntupalli --- ADVENTURES.md | 9 +++++---- CLAUDE.md | 4 +++- CONTRIBUTING.md | 2 +- PERFORMANCE.md | 2 +- README.md | 8 +++++--- src/components/ArchitectureSection.tsx | 3 ++- src/components/AvatarLink.tsx | 1 + src/components/BoardSection.tsx | 1 + src/components/BottomCTA.tsx | 1 + src/components/ChallengesGrid.tsx | 4 ++-- src/components/DiscussionSection.tsx | 1 + src/components/Footer.tsx | 2 +- src/components/Hero.tsx | 4 ++-- src/components/PageHero.tsx | 4 ++-- src/pages/About.tsx | 6 +++++- src/pages/Sponsors.tsx | 2 +- styleguide.md | 20 +++++++++++--------- 17 files changed, 45 insertions(+), 29 deletions(-) diff --git a/ADVENTURES.md b/ADVENTURES.md index b333694f2..56eeea5a0 100644 --- a/ADVENTURES.md +++ b/ADVENTURES.md @@ -177,10 +177,11 @@ When a new level is ready in the challenges repo after the first adventure PR ha These scripts run automatically on the hourly schedule but can also be run locally. ```sh -# Requires DISCOURSE_API_KEY and DISCOURSE_API_USERNAME in .env -node scripts/refresh-discussions.mjs # Fetch discussion posts for each level -node scripts/refresh-leaderboard.mjs # Fetch leaderboard data per adventure/level -node scripts/refresh-community-leaders.mjs # Fetch community leader data +node scripts/refresh-discussions.mjs # Fetch discussion posts for each level (no credentials needed) + +# The following two scripts require DISCOURSE_API_KEY and DISCOURSE_API_USERNAME in .env +node scripts/refresh-leaderboard.mjs # Fetch leaderboard data per adventure/level +node scripts/refresh-community-leaders.mjs # Fetch community leader data ``` Create a `.env` file at the repo root for local use: diff --git a/CLAUDE.md b/CLAUDE.md index a0c51d7f5..707df2382 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -91,6 +91,8 @@ public/ refresh-community-sitemap.yml # Daily community sitemap regeneration sync-adventure.yml # workflow_dispatch: sync an adventure from the challenges repo validate-adventures.yml # PR check: validates adventure YAML, routes, and sitemap consistency + validate-docs.yml # PR check: ensures styleguide.md/README.md updated with code changes + add-discussion-url.yml # workflow_dispatch: set discussionUrl for a level and fetch initial posts ``` --- @@ -199,7 +201,7 @@ When diagnosing a bug, especially in the production build, follow these rules wi - **Author-controlled prose fields contain pre-rendered HTML.** Every YAML/TS field that holds prose written by a challenge author (`level.audience`, `tool.description`, `step.title`, `step.content`, `contributor.about`, `rewards.eligibility`, `tier.description`, `rewards.rankingNote`, `level.learnings`, `level.objective`, `level.intro`, `level.backstory`, `level.scenario`, `level.architecture`, `adventure.story`, `adventure.backstory`) is converted from Markdown to sanitised HTML at build time by `scripts/generate-adventures.mjs`. Always render them with `dangerouslySetInnerHTML={{ __html: value }}` and the `md-inline` (inline prose) or `md-content` (block content) CSS class. Never render as `{value}` directly. Identifier fields (`id`, URLs, enum values like `difficulty`, emoji) are not author prose and are rendered directly. - **When the container is an interactive element** (e.g. a `` card or a ` + + + +
+ {(onDark ? DARK_COLORS : LIGHT_COLORS).map((swatch) => ( + + ))} +
+ + {!onDark && ( +
+

Amber in light mode

+

+ Amber{" "} + #ffc034{" "} + passes contrast only as a fill or border. For amber-toned link hover text, use{" "} + hsl(41 100% 25%),{" "} + the --link-hover-light token. +

+
+ )} + + + + {/* Typography */} + +

+ All fonts are self-hosted as WOFF2. No external requests. Use the{" "} + font-heading,{" "} + font-sans, and{" "} + font-mono{" "} + Tailwind utilities. +

+ +
+
+

Syne

+ font-heading / 700 / Headings and display +
+
+

Open Source

+

Community First

+

Build Together

+

Vendor-Neutral

+

Always Learning

+
+
+ +
+
+

Inter

+ font-sans / 400-700 / Body and UI +
+
+ {FONT_WEIGHTS.map(({ weight, label, sample }) => ( +
+ {weight} {label} +

{sample}

+
+ ))} +
+
+ +
+
+

JetBrains Mono

+ font-mono / 400-600 / Code and tokens +
+
+
+                        {`--primary: 41 100% 60%;
+--font-heading: Syne, 'Syne Fallback', sans-serif;
+--background: 0 0% 4%;
+hsl(var(--foreground))`}
+                      
+
+
+
+ + {/* Design Elements */} + + +

Mascot: Nyx

+

+ Nyx is the {BRAND_NAME} firefly mascot. Nyx is gender neutral, so avoid gendered pronouns. Use Nyx in event materials, swag, and community campaigns. Don't alter the colors, proportions, or shape. +

+
+
+
+ Nyx, the OffOn firefly mascot, full view +
+

Nyx: Full

+

Hero sections, event banners, swag.

+ +
+
+
+ Nyx the OffOn firefly mascot, peeking variant +
+

Nyx: Peek

+

Page hero backgrounds, corner decorations.

+ +
+
+ +

Open Graph Image

+

+ Used as the social media preview when {BRAND_NAME} links are shared. Dimensions: 1200 x 630 px. +

+
+ OffOn Open Graph preview image, 1200 by 630 pixels +
+ +
+ + {/* Photography */} + +

+ When photography appears in {BRAND_NAME} materials, it should feel raw and technical, not polished stock imagery. +

+ +
+
+

+

+
    +
  • Real developers at real terminals
  • +
  • Dark environments that echo the dark mode palette
  • +
  • Collaborative scenes with multiple contributors
  • +
  • Warm amber or neutral accent lighting
  • +
  • Diverse representation of the open source community
  • +
  • Legible code on screens where visible
  • +
+
+
+

+

+
    +
  • Stock photos of people smiling at laptops
  • +
  • Bright, corporate-feeling environments
  • +
  • Heavily retouched or filtered images
  • +
  • Low-quality clipart or filler illustrations
  • +
  • Photos with visible vendor brand logos
  • +
  • Images that imply exclusion
  • +
+
+
+ +
+

When photos aren't available

+

+ Use the Nyx mascot or abstract dark/amber compositions. A clean empty section is better than a distracting stock photo. +

+
+
+ + {/* Voice and Tone */} + + +
+

Brand Name

+
+

{BRAND_NAME}

+

+ Always camelCase. Domain is always lowercase:{" "} + offon.dev +

+
+ {[ + { text: "OffOn", ok: true }, + { text: "offon.dev", ok: true }, + { text: "Offon", ok: false }, + { text: "OFFON", ok: false }, + { text: "offon", ok: false }, + { text: "Off-On", ok: false }, + ].map(({ text, ok }) => ( +
+ {ok + ?
+ ))} +
+
+
+ +
+

Slogans

+
+
+

Primary

+

+ {BRAND_SLOGAN_PARTS.join(". ")}. +

+
+
+

Secondary

+

{BRAND_SECONDARY_LINE}

+

+ The lowercase "always" is intentional. +

+
+ {BRAND_SECONDARY_LINE_PARTS.map((part) => ( + {part} + ))} +
+
+
+
+ +
+

Tone

+
+
+

We are

+
    +
  • Direct and to the point
  • +
  • Focused on the community, not the brand
  • +
  • Inclusive and welcoming
  • +
  • Plain-spoken, no unnecessary jargon
  • +
  • Active voice wherever possible
  • +
+
+
+

We are not

+
    +
  • Corporate or formal
  • +
  • Hype-driven or hollow
  • +
  • Exclusive or gatekeeping
  • +
  • Vendor-aligned in language
  • +
  • Passive or hedging
  • +
+
+
+
+
+ + {/* Accessibility */} + +

+ Every page on {BRAND_NAME} targets WCAG 2.2 Level AA in both light and dark mode. This is the floor, not the goal. The full statement, testing approach, and how to report a barrier are in the{" "} + Accessibility Statement. +

+ +
+
+

Color and contrast

+
    +
  • Body text: minimum 4.5:1 contrast in both modes
  • +
  • Large text and UI controls: minimum 3:1
  • +
  • Never use amber #ffc034 as text in light mode
  • +
  • Hover states must meet contrast in both light and dark
  • +
  • Never convey meaning through color alone
  • +
+
+
+

Interaction and motion

+
    +
  • Every interactive element is keyboard reachable
  • +
  • Focus rings visible in both modes on all elements
  • +
  • Touch targets minimum 24 x 24 px (prefer 44 x 44 px)
  • +
  • Animations respect prefers-reduced-motion
  • +
  • All images have descriptive alt text
  • +
+
+
+
+ + + + {/* Sticky right TOC — visible to screen readers, not aria-hidden */} + + + + + + + +