Skip to content

feat: Complete Elixir School v2.0 rebrand and redesign#245

Draft
doomspork wants to merge 53 commits intomainfrom
doomspork/rebrand-redesign
Draft

feat: Complete Elixir School v2.0 rebrand and redesign#245
doomspork wants to merge 53 commits intomainfrom
doomspork/rebrand-redesign

Conversation

@doomspork
Copy link
Copy Markdown
Member

Summary

Complete redesign and rebrand of Elixir School with Phoenix 1.7 upgrade, new design system, and comprehensive template updates across all pages.

Changes

Phase 0: Phoenix 1.7 Migration & Dependency Upgrades

  • Upgraded Phoenix from 1.6.9 to 1.7
  • Updated phoenix_html to 4.0, phoenix_live_view to 0.20, esbuild to 0.8
  • Removed deprecated :phoenix compiler
  • Updated flash handling to use Phoenix.Flash API
  • Converted views to use Phoenix.Component patterns

Phase 1: Design System Foundation

  • Implemented comprehensive CSS custom property design tokens
  • Added semantic color palette (surface, on-surface, brand, accent colors)
  • Migrated dark mode from .dark class to data-theme="dark" attribute
  • Tailwind 3.4 configuration with darkMode selector strategy
  • Added Google Fonts: Outfit, DM Sans, JetBrains Mono
  • Purple-based syntax highlighting theme for code blocks

Phase 2: Global Components (Nav, Footer, Layout)

  • Fixed Navigation Bar: 64px frosted glass navbar with backdrop-filter blur
  • Header Redesign: Logo, nav links with hover animations, locale selector, theme toggle, GitHub link
  • Footer Redesign: 4-column layout with Brand, Learn, Resources, and Community sections
  • Lesson Menu: Converted from CSS toggle to Alpine.js dropdown with smooth animations
  • Dark Mode Toggle: Circular button with sun/moon SVG icons
  • Locale Menu: Compact pill dropdown with grouped language options
  • Layout Updates: Added pt-16 offset for fixed navbar, updated all layout templates
  • Report Templates: Restyled with new design system colors and typography

Phases 3-6: Page Redesigns

  • Homepage: Two-column hero with code window, stats bar, feature cards, blog preview
  • Lesson Pages: Three-column layout with sidebar navigation, sticky TOC, breadcrumbs
  • Blog Pages: Redesigned card layouts, improved post previews and archive
  • Secondary Pages: Why Elixir, Get Involved, Podcasts, Conferences with new visual hierarchy
  • All Templates: Consistent use of new typography (Outfit headings, DM Sans body, JetBrains Mono code)

Technical Details

Design System

  • Colors: 9-step purple gradient + semantic colors (surface, text, border, accent)
  • Typography: Outfit 300-800 (headings), DM Sans 300-700 (body), JetBrains Mono 400-600 (code)
  • Spacing: Consistent grid-based spacing with design-system-aligned utilities
  • Shadows & Radius: CSS-variable-based design tokens for visual consistency
  • Dark Mode: Automatic theme switching via data-theme attribute with CSS variables

Components

  • Alpine.js for interactive dropdowns and mobile navigation
  • CSS animations for hover states and transitions (0.2s cubic-bezier)
  • Responsive design patterns (3-column → 2-column → 1-column at breakpoints)
  • Frosted glass effects on fixed navigation with backdrop-filter

Testing

  • All 57 existing tests pass
  • No breaking changes to routing or data structures
  • Backward compatibility maintained through Phoenix 1.7 patterns

Deployment Notes

  • Package.json updated with Tailwind 3.4+ required for darkMode selector strategy
  • Tailwind compilation may take slightly longer with new utility generation
  • CSS custom properties require modern browser support (all major browsers supported)

Files Changed

  • 54 files modified
  • Mix dependencies upgraded (Phoenix, LiveView, HTML)
  • Assets updated (Tailwind config, CSS, JavaScript)
  • All template files (layout, lesson, page, post, report, live)

🚀 Ready for review and merge

Copilot AI review requested due to automatic review settings February 16, 2026 15:03
@doomspork doomspork requested a review from a team as a code owner February 16, 2026 15:03
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR completes the Elixir School v2.0 rebrand/redesign while upgrading the app to Phoenix 1.7 and modernizing the asset/tooling pipeline (Tailwind, Esbuild, LiveView patterns).

Changes:

  • Upgraded Phoenix/LiveView + related deps and updated flash/dark-mode handling to newer Phoenix 1.7 patterns.
  • Introduced a CSS-variable-driven design system and refreshed global layouts/components and most page templates.
  • Switched lesson/blog content ingestion to a git submodule workflow and updated build/deploy scripts accordingly.

Reviewed changes

Copilot reviewed 51 out of 54 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/school_house_web/views/layout_view_test.exs Updates dark-mode helper expectations for new nil behavior.
mix.lock Locks updated dependency versions for Phoenix 1.7 + related packages.
mix.exs Updates Phoenix/LiveView deps, removes deprecated Phoenix compiler, bumps esbuild wrapper.
lib/school_house_web/views/layout_view.ex Changes dark-mode helper to return nil instead of empty string.
lib/school_house_web/templates/report/report.html.heex Restyles report page with new spacing/typography tokens.
lib/school_house_web/templates/report/_section.html.heex Restyles report tables to match new design system.
lib/school_house_web/templates/report/_missing.html.heex Updates missing-row styling and semantic colors.
lib/school_house_web/templates/report/_lesson.html.heex Updates lesson-row styling and semantic colors.
lib/school_house_web/templates/report/_coming_soon.html.heex Updates “coming soon” row styling to new tokens.
lib/school_house_web/templates/post/post.html.heex Redesigns blog post show page with new layout/typography.
lib/school_house_web/templates/post/index.html.heex Redesigns blog index grid and pagination UI.
lib/school_house_web/templates/post/_post_preview.html.heex Updates blog preview cards to new design system components.
lib/school_house_web/templates/page/why.html.heex Major redesign of Why page into card/grid-based sections.
lib/school_house_web/templates/page/podcasts.html.heex Redesigns podcasts page into card grid layout.
lib/school_house_web/templates/page/index.html.heex Full homepage redesign (hero, stats, features, CTA).
lib/school_house_web/templates/page/get_involved.html.heex Redesigns contribution page into card-based sections.
lib/school_house_web/templates/lesson/lesson.html.heex Redesigns lesson page layout (title, excerpt, TOC, body, CTA).
lib/school_house_web/templates/lesson/index.html.heex Redesigns lesson index list into styled cards.
lib/school_house_web/templates/lesson/_testing.html.heex Restyles lesson section header (Testing).
lib/school_house_web/templates/lesson/_storage.html.heex Restyles lesson section header (Storage).
lib/school_house_web/templates/lesson/_section_header.html.heex Updates in-lesson heading/link styling and scroll offset.
lib/school_house_web/templates/lesson/_pagination.html.heex Simplifies/restyles lesson pagination component.
lib/school_house_web/templates/lesson/_misc.html.heex Restyles lesson section header (Misc).
lib/school_house_web/templates/lesson/_intermediate.html.heex Restyles lesson section header (Intermediate).
lib/school_house_web/templates/lesson/_ecto.html.heex Restyles lesson section header (Ecto).
lib/school_house_web/templates/lesson/_data_processing.html.heex Restyles lesson section header (Data Processing).
lib/school_house_web/templates/lesson/_basics.html.heex Restyles lesson section header (Basics).
lib/school_house_web/templates/lesson/_advanced.html.heex Restyles lesson section header (Advanced).
lib/school_house_web/templates/layout/root.html.heex Switches dark-mode to data-theme, updates title helper, adds fonts, adjusts layout padding.
lib/school_house_web/templates/layout/live.html.heex Updates LiveView flash rendering to Phoenix.Flash API + new styling.
lib/school_house_web/templates/layout/app.html.heex Updates controller layout flash rendering to Phoenix.Flash API + new styling.
lib/school_house_web/templates/layout/_locale_menu.html.heex Redesigns locale selector UI using new tokens.
lib/school_house_web/templates/layout/_lesson_menu.html.heex Replaces mega-menu checkbox toggle with Alpine.js dropdown.
lib/school_house_web/templates/layout/_header.html.heex Replaces header/nav with fixed frosted-glass navbar + mobile menu.
lib/school_house_web/templates/layout/_footer.html.heex Rebuilds footer into 4-column design system layout.
lib/school_house_web/templates/layout/_dark_mode_toggle.html.heex Replaces slider toggle with icon button toggle.
lib/school_house_web/live/conferences_live.html.heex Restyles conferences page + modernizes filter form structure.
lib/school_house_web.ex Switches HTML helpers import to Phoenix.Component for newer patterns.
content Adds git submodule pointer for lessons/blog content checkout.
config/config.exs Bumps configured esbuild binary version.
assets/tailwind.config.js Adds design tokens + selector-based dark mode + typography updates.
assets/package.json Updates Tailwind/PostCSS/Alpine/topbar versions for new build pipeline.
assets/js/menu-toggle.js Updates mobile menu toggle behavior for new header DOM.
assets/js/initialize-theme.js Updates initial dark-mode detection to use data-theme.
assets/js/dark-mode.js Updates theme toggle implementation to use data-theme.
assets/js/app.js Removes pickup import and wires new JS modules.
assets/css/app.css Introduces CSS custom properties design system + updated syntax highlighting and transitions.
README.md Updates project description and adds submodule-based setup/docs.
Makefile Switches content acquisition from git clone to submodule update + image copy.
Dockerfile Copies submodule content into build image and extracts images.
.gitmodules Adds content submodule configuration.
.gitignore Stops ignoring /content so submodule can be committed.
.github/workflows/deploy.yml Ensures deploy workflow checks out git submodules.
Comments suppressed due to low confidence (3)

test/school_house_web/views/layout_view_test.exs:1

  • In tests, prefer assert is_nil(...) (or pattern matching) over comparing with == nil. It reads more idiomatically and produces clearer failures.
    lib/school_house_web/templates/page/podcasts.html.heex:1
  • Links opened with target=\"_blank\" should include rel=\"noopener noreferrer\" to prevent reverse-tabnabbing and to avoid leaking window.opener. Add the rel attribute here (and any other external _blank links in this template).
    lib/school_house_web/templates/page/why.html.heex:1
  • This external link uses target=\"_blank\" without rel=\"noopener noreferrer\", which is a security risk (reverse-tabnabbing). Add rel=\"noopener noreferrer\" here (and apply the same fix to other _blank links in this page).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md Outdated
Comment thread lib/school_house_web/templates/layout/_locale_menu.html.heex Outdated
@doomspork doomspork force-pushed the doomspork/rebrand-redesign branch from 1c142f3 to 14188fc Compare February 16, 2026 16:34
@doomspork doomspork marked this pull request as draft February 16, 2026 16:58
@doomspork doomspork mentioned this pull request Feb 16, 2026
5 tasks
@brain-geek
Copy link
Copy Markdown
Contributor

Congrats on this major rework!

@jaimeiniesta
Copy link
Copy Markdown
Contributor

jaimeiniesta commented Apr 6, 2026

Is this branch live on some staging site? I'd like to review it from the accessibility point of view if you're interested :)

If it's not I can still review it on my local dev machine.

@doomspork
Copy link
Copy Markdown
Member Author

Hey @jaimeiniesta! I was just thinking about you as I was doing a little more on this revamp tonight. I don't have this deployed yet but let me see what it would take to get it up somewhere.

@doomspork doomspork force-pushed the doomspork/rebrand-redesign branch from 4dfcaad to 6116700 Compare April 7, 2026 00:57
doomspork added 20 commits April 6, 2026 21:30
Upgrade to Phoenix 1.7, LiveView 0.20, phoenix_html 4.0, Tailwind 3.4,
and esbuild 0.8. Introduce a new indigo-purple design system with CSS
custom properties for light/dark themes, semantic color tokens (surface,
brand, accent), and Google Fonts (Outfit, DM Sans, JetBrains Mono).

Redesign all templates: frosted-glass nav with Alpine.js dropdowns,
hero section with code window, card-based layouts for lessons/blog/
secondary pages, gradient accent bars, hover effects, and responsive
grid layouts throughout.
Replace the `make content` clone-and-delete workflow with a git
submodule pointing at elixirschool/elixirschool. This pins content
to a specific commit for reproducible builds and faster local
iteration. Update Makefile, Dockerfile, and CI workflow accordingly.
Rewrite the README to document the git submodule setup for content,
including clone with --recursive, submodule initialization, content
update workflow, prerequisites, and Docker build instructions.
Remove extra `&` entity before the arity in the decorative code
example, changing rendered output from `&hello/&1` to `&hello/1`.
- Align README prerequisites with mix.exs elixir version constraint
- Bind aria-expanded to Alpine.js state in locale menu for accessibility
- Add aria-controls to locale menu button linking to dropdown
- Use idiomatic assert is_nil() in layout view test
- Add rel="noopener noreferrer" to all external target="_blank" links
  across podcasts, get_involved, why, conferences, and lesson templates
- Delete unused pickup.js (dead code since Phase 2)
- Remove legacy color definitions from Tailwind config (brand-purple-*,
  brand-gray-*, brand-red-*, and semantic backgroundColor/textColor mappings)
- Remove unused tailwindcss/colors import
- Restyle error pages and newsletter partial to new design system
- Update html_helpers.ex lesson_link and coming_soon_badge classes
- Re-extract gettext strings across all 24 locale PO files
The section header template was updated with new design system classes
(font-heading, text-on-surface, hover:text-brand-500, etc.) but the
test expectation still had the old classes (flex, pt-1 ml-2, w-6 h-6).
- Update mix.exs elixir constraint from ~> 1.13 to ~> 1.18
- Update CI workflow from Elixir 1.14.1/OTP 25 to 1.18.4/OTP 27
- Update README prerequisites to match
- Add mise.toml alongside existing .tool-versions
Move site brand assets (logo, icon, favicons) from the content image
pipeline to assets/static/brand/, served directly by Phoenix. Content
images are now exclusively for lessons and blog posts.
Fill 19 empty msgstr entries for new UI strings from the redesign.
Preserves all existing translations by native speakers.
Fill 19 empty msgstr entries for new UI strings from the redesign.
Preserves all existing translations by native speakers.
Fill 25 empty msgstr entries including new redesign UI strings.
Preserves all existing translations by native speakers.
Fill 25 empty msgstr entries including new redesign UI strings.
Preserves all existing translations by native speakers.
Fill 29 empty msgstr entries including new redesign UI strings.
Preserves all existing translations by native speakers.
Fill 207 empty msgstr entries with German translations covering
all UI strings, navigation, lesson titles, and feature descriptions.
Fill 207 empty msgstr entries with French translations covering
all UI strings, navigation, lesson titles, and feature descriptions.
Fill 207 empty msgstr entries with Portuguese translations covering
all UI strings, navigation, lesson titles, and feature descriptions.
Fill 207 empty msgstr entries with Italian translations covering
all UI strings, navigation, lesson titles, and feature descriptions.
doomspork added 29 commits April 6, 2026 21:30
Update esbuild target from es2016 to es2017 to preserve async/await.
Alpine.js v3 uses async function evaluation internally and calls
.catch() on the result. With es2016, esbuild transpiled async
functions into generator-based code that doesn't return a Promise,
silently breaking all Alpine expression evaluation (x-data, x-show).

Fix lesson_link default class from block to inline and outer wrapper
from flex to contents so numbered list markers in the lesson dropdown
render inline with lesson names instead of on separate lines.
Add a dedicated /ecosystem page highlighting key Elixir ecosystem
projects: Phoenix & LiveView, Livebook, Nx & Bumblebee, Nerves, Ecto,
Oban, Broadway, Ash Framework, and Hologram. Include an ecosystem
highlights section on the homepage with a CTA linking to the full page.
Update header, footer, and homepage navigation accordingly.
Redesign the lesson page from a single-column layout to a 3-column
grid with left sidebar (lesson navigation), main content area, and
right sidebar (table of contents + action links).

- Add filtered_lessons/0 to Lessons module for section data access
- Update LessonController to pass all_sections for sidebar rendering
- Create _lesson_sidebar.html.heex with section groups and active state
- Create _toc_sidebar.html.heex with TOC links and GitHub action buttons
- Add breadcrumb navigation to lesson content area
- Integrate prev/next navigation inline in content area
- Add responsive CSS: TOC hidden at 1100px, sidebar hidden at 768px
- Both sidebars use sticky positioning aligned with 64px navbar
- Wrap lesson layout CSS in @layer components for proper Tailwind
  integration and CSS ordering
- Add min-width: 0 and overflow-x: auto to .lesson-content to prevent
  code blocks from expanding the grid column beyond its bounds
- Replace en-dash characters in CSS comments with ASCII dashes
Instead of showing all 8 sections with every lesson, the left sidebar
now displays only lessons within the current section for a cleaner,
more focused navigation experience.
The lesson markdown files contain {{ site.erlang.OTP }},
{{ site.erlang.erts }}, and {{ site.elixir.version }} placeholders
from the original Jekyll site that were rendering as raw text.
These are now replaced at compile time with actual BEAM version
values so they stay current automatically.
Track completed lessons and last-visited position using an Alpine.js
store backed by localStorage. Lessons auto-complete when the user
scrolls to the bottom (via IntersectionObserver with a 2s guard for
short lessons). The sidebar shows green checkmarks for completed
lessons and a section counter (e.g. "3/15"). A "Continue Learning"
card on the homepage links to the last visited lesson.
- Add Sponsors GenServer that fetches sponsor data from GitHub GraphQL API
- Cache sponsor data in ETS with 15-minute TTL for fast reads
- Create dedicated /sponsors page with tiers, sponsor grid, and CTA
- Add sponsors bar on homepage (shown only when featured sponsors exist)
- Add "Sponsor the Project" card to Get Involved page
- Add sponsor link with heart icon to footer Community column
- Extract new gettext strings across all locales
- Remove accidental match on ETS table init (was `@table = :ets.new(...)`)
- Guard against empty string GITHUB_TOKEN in addition to nil
- Add Logger.warning on all API failure paths for prod observability
- Add smoke test for the /sponsors route
- Format sponsors.ex (long lines in default_tiers)
- Fix Credo single-function pipeline warning in featured/0
- Update picomatch and yaml to fix high/moderate vulnerabilities
- Add minimatch override (^3.1.4) to resolve remaining 2 high alerts
- npm audit now reports 0 vulnerabilities
Address 19 accessibility issues across the redesigned site:

- Add skip navigation link and main landmark to root layout
- Fix muted text contrast ratios in both light and dark themes
- Add aria-expanded, aria-controls, and keyboard support to menus
- Refactor dark mode toggle to use aria-pressed (remove redundant checkbox)
- Add aria-hidden to ~42 decorative SVGs across all pages
- Add "opens in new tab" screen reader text to external links
- Add landmark labels to lesson sidebars and homepage sections
- Add breadcrumb aria-label and aria-current to lesson navigation
- Add role="status" live region to lesson completion toast
- Add role="menuitem" to locale dropdown links
- Add mobile lesson navigation drawer for sidebar access on small screens
- Mark hero code blocks as role="img" with descriptive labels
- Add global focus-visible outline styles
- Add prefers-reduced-motion media query
- Fix podcast card redundant link structure
- Add newsletter form validation aria-describedby
- Update footer copyright year to be dynamic
Fill ~62 empty msgstr entries for Norwegian and Spanish including
ecosystem tool descriptions, sponsor section, and code showcase strings.
Fill ~75 empty msgstr entries for Korean and Greek including
ecosystem tool descriptions, sponsor section, and previously
untranslated UI strings.
Fill ~65 empty msgstr entries for Japanese including ecosystem
tool descriptions, sponsor section, and code showcase strings.
Fill ~64 empty msgstr entries each for German and French including
ecosystem tool descriptions, sponsor section, and code showcase strings.
Fill ~64 empty msgstr entries each for Portuguese and Italian including
ecosystem tool descriptions, sponsor section, and code showcase strings.
Fill ~64 empty msgstr entries each for Russian and Polish including
ecosystem tool descriptions, sponsor section, and code showcase strings.
Fill ~64 empty msgstr entries each for Arabic, Turkish, and Indonesian
including ecosystem tool descriptions, sponsor section, and code
showcase strings.
Fill ~35 remaining empty msgstr entries for Bulgarian including
sponsor section strings and a few ecosystem descriptions.
Translate all 270 strings for Ukrainian locale which was previously
untranslated (0% complete). Covers lessons, UI, ecosystem, and
sponsor sections.
Translate all 270 strings for Vietnamese locale which was previously
untranslated (0% complete). Covers lessons, UI, ecosystem, and
sponsor sections.
Translate all 270 strings for Slovak locale which was previously
untranslated (0% complete). Covers lessons, UI, ecosystem, and
sponsor sections.
Translate all 270 strings for Bengali locale which was previously
untranslated (0% complete). Covers lessons, UI, ecosystem, and
sponsor sections.
Translate all 270 strings for Persian/Farsi locale which was previously
untranslated (0% complete). Covers lessons, UI, ecosystem, and
sponsor sections.
Translate all 270 strings for Malay locale which was previously
untranslated (0% complete). Covers lessons, UI, ecosystem, and
sponsor sections.
Translate all 270 strings for Tamil locale which was previously
untranslated (0% complete). Covers lessons, UI, ecosystem, and
sponsor sections.
Translate all 270 strings for Thai locale which was previously
untranslated (0% complete). Covers lessons, UI, ecosystem, and
sponsor sections.
…uality

- Validate locale against whitelist before dynamic atom creation in HtmlHelpers
- Use GraphQL variables instead of string interpolation in Sponsors
- Add request timeout (15s) to GitHub API calls in Sponsors
- Change ETS table from :public to :protected in Sponsors
- Guard against nil section in populate_surrounding_lessons
- Wrap String.to_existing_atom in try/rescue in Lessons.list/2
- Standardize error tuples to 2-tuple {:error, {:translation_not_found, locale}}
- Validate locale param in PageController.report/2
- URI-encode dynamic params in GitHub issue URLs
- Add error message for malformed version strings in Lesson
- Consolidate duplicate .sidebar-section-title CSS rules
- Scope dark mode transitions to specific elements instead of body *
- Use window load event instead of setTimeout for preload removal
- Extract TOAST_DURATION constant in progress.js
@doomspork doomspork force-pushed the doomspork/rebrand-redesign branch from ca0c136 to 074aaf1 Compare April 7, 2026 02:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants