diff --git a/fitnessfunctions.html b/fitnessfunctions.html index 962bcb8..768f298 100644 --- a/fitnessfunctions.html +++ b/fitnessfunctions.html @@ -6,20 +6,62 @@ - - - - - - + + + + + + + +

FitnessFunctions

@@ -34,7 +76,7 @@

FitnessFunctions

-

Overview

+

Overview

FitnessFunctions is a solo-built role-based web app that manages gym instructors, class schedules, and member enrollments. I designed and implemented the full system end-to-end: @@ -43,7 +85,7 @@

Overview

and drop classes. Enrollment totals update per class to enforce capacity limits.

-

Key Features

+

Key Features

  • Solo Project: Role-based class enrollment system · PHP + MySQL
  • Authentication + Roles: users log in and are routed to Admin or Member dashboards.
  • @@ -53,7 +95,7 @@

    Key Features

  • Capacity Tracking: “Enrolled” count updates and classes can show Full.
-

Technologies

+

Technologies

  • Frontend: HTML, CSS (custom neon dashboard UI)
  • Backend: PHP
  • @@ -61,76 +103,89 @@

    Technologies

  • Environment: Apache (localhost development)
-

Screenshots

+

Screenshots

- FitnessFunctions login screen + FitnessFunctions login screen
Login page with test accounts.
- FitnessFunctions admin dashboard + FitnessFunctions admin dashboard
Admin dashboard navigation.
- Manage instructors page + Manage instructors page
Admin: Manage instructors (CRUD).
- Manage classes page + Manage classes page
Admin: Manage classes (CRUD + capacity).
- Class enrollments page + Class enrollments page
Admin: View class enrollments (who is enrolled in each class).
- Member dashboard + Member dashboard
Member dashboard.
- Available classes page + Available classes page
Member: Browse available classes and see enrollment counts.
- My classes page + My classes page
Member: View enrolled classes and drop if needed.
-

Implementation Proof (Business Logic + Data)

+

Implementation Proof (Business Logic + Data)

- Add class form + Add class form
Add Class form (admin). Scheduling + instructor assignment + capacity input.
- Validation when submitting empty form + Validation when submitting empty form
Client-side validation: required fields prevent submitting an empty form.
- Capacity full example + Capacity full example
Capacity logic: class marked Full when enrollment reaches capacity.
- Database schema in MySQL Workbench + Database schema in MySQL Workbench
Database schema (MySQL) showing core tables: users, instructors, classes, enrollments.
- Project file structure in VS Code + Project file structure in VS Code
Project structure (PHP pages + handlers + DB connection).
-

Download

+

Download

You can download the full project files here: FitnessFunctions.zip @@ -145,30 +200,5 @@

Download

Back to Resume

- - diff --git a/images/1.png b/images/1.png new file mode 100644 index 0000000..a6f8c54 Binary files /dev/null and b/images/1.png differ diff --git a/images/2.png b/images/2.png new file mode 100644 index 0000000..049563b Binary files /dev/null and b/images/2.png differ diff --git a/images/bg-bottom.gif b/images/bg-bottom.gif new file mode 100644 index 0000000..e26c522 Binary files /dev/null and b/images/bg-bottom.gif differ diff --git a/images/favicon-16.png b/images/favicon-16.png deleted file mode 100644 index 4774895..0000000 Binary files a/images/favicon-16.png and /dev/null differ diff --git a/images/favicon-180.png b/images/favicon-180.png index 703521a..9b5f920 100644 Binary files a/images/favicon-180.png and b/images/favicon-180.png differ diff --git a/images/favicon-192.png b/images/favicon-192.png index 558df09..33052fa 100644 Binary files a/images/favicon-192.png and b/images/favicon-192.png differ diff --git a/images/favicon-256.png b/images/favicon-256.png deleted file mode 100644 index 6de326f..0000000 Binary files a/images/favicon-256.png and /dev/null differ diff --git a/images/favicon-32.png b/images/favicon-32.png deleted file mode 100644 index 92d5ae7..0000000 Binary files a/images/favicon-32.png and /dev/null differ diff --git a/images/favicon-48.png b/images/favicon-48.png deleted file mode 100644 index fe0b1b5..0000000 Binary files a/images/favicon-48.png and /dev/null differ diff --git a/images/favicon-512.png b/images/favicon-512.png deleted file mode 100644 index 8d48c8d..0000000 Binary files a/images/favicon-512.png and /dev/null differ diff --git a/images/favicon-64.png b/images/favicon-64.png deleted file mode 100644 index 766abe9..0000000 Binary files a/images/favicon-64.png and /dev/null differ diff --git a/images/favicon.ico b/images/favicon.ico index e7448e5..3b064ff 100644 Binary files a/images/favicon.ico and b/images/favicon.ico differ diff --git a/images/fitnessfunctions/addClass.png b/images/fitnessfunctions/addClass.png deleted file mode 100644 index b3386a5..0000000 Binary files a/images/fitnessfunctions/addClass.png and /dev/null differ diff --git a/images/fitnessfunctions/addClass.webp b/images/fitnessfunctions/addClass.webp new file mode 100644 index 0000000..82311a4 Binary files /dev/null and b/images/fitnessfunctions/addClass.webp differ diff --git a/images/fitnessfunctions/adminDashboard.png b/images/fitnessfunctions/adminDashboard.png deleted file mode 100644 index 53ce71a..0000000 Binary files a/images/fitnessfunctions/adminDashboard.png and /dev/null differ diff --git a/images/fitnessfunctions/adminDashboard.webp b/images/fitnessfunctions/adminDashboard.webp new file mode 100644 index 0000000..3db2cd8 Binary files /dev/null and b/images/fitnessfunctions/adminDashboard.webp differ diff --git a/images/fitnessfunctions/availableClasses.png b/images/fitnessfunctions/availableClasses.png deleted file mode 100644 index ef56ce0..0000000 Binary files a/images/fitnessfunctions/availableClasses.png and /dev/null differ diff --git a/images/fitnessfunctions/availableClasses.webp b/images/fitnessfunctions/availableClasses.webp new file mode 100644 index 0000000..3d58145 Binary files /dev/null and b/images/fitnessfunctions/availableClasses.webp differ diff --git a/images/fitnessfunctions/capacityFull.png b/images/fitnessfunctions/capacityFull.png deleted file mode 100644 index 62ce1be..0000000 Binary files a/images/fitnessfunctions/capacityFull.png and /dev/null differ diff --git a/images/fitnessfunctions/capacityFull.webp b/images/fitnessfunctions/capacityFull.webp new file mode 100644 index 0000000..9c04b2d Binary files /dev/null and b/images/fitnessfunctions/capacityFull.webp differ diff --git a/images/fitnessfunctions/classEnrollments.png b/images/fitnessfunctions/classEnrollments.png deleted file mode 100644 index 0963877..0000000 Binary files a/images/fitnessfunctions/classEnrollments.png and /dev/null differ diff --git a/images/fitnessfunctions/classEnrollments.webp b/images/fitnessfunctions/classEnrollments.webp new file mode 100644 index 0000000..69522f2 Binary files /dev/null and b/images/fitnessfunctions/classEnrollments.webp differ diff --git a/images/fitnessfunctions/codeStructure.png b/images/fitnessfunctions/codeStructure.png deleted file mode 100644 index 7d02dc2..0000000 Binary files a/images/fitnessfunctions/codeStructure.png and /dev/null differ diff --git a/images/fitnessfunctions/codeStructure.webp b/images/fitnessfunctions/codeStructure.webp new file mode 100644 index 0000000..51ec392 Binary files /dev/null and b/images/fitnessfunctions/codeStructure.webp differ diff --git a/images/fitnessfunctions/databaseSchema.png b/images/fitnessfunctions/databaseSchema.png deleted file mode 100644 index 474095c..0000000 Binary files a/images/fitnessfunctions/databaseSchema.png and /dev/null differ diff --git a/images/fitnessfunctions/databaseSchema.webp b/images/fitnessfunctions/databaseSchema.webp new file mode 100644 index 0000000..16f1e1f Binary files /dev/null and b/images/fitnessfunctions/databaseSchema.webp differ diff --git a/images/fitnessfunctions/login.png b/images/fitnessfunctions/login.png deleted file mode 100644 index a94fb21..0000000 Binary files a/images/fitnessfunctions/login.png and /dev/null differ diff --git a/images/fitnessfunctions/login.webp b/images/fitnessfunctions/login.webp new file mode 100644 index 0000000..21a068d Binary files /dev/null and b/images/fitnessfunctions/login.webp differ diff --git a/images/fitnessfunctions/manageClasses.png b/images/fitnessfunctions/manageClasses.png deleted file mode 100644 index aa0c63b..0000000 Binary files a/images/fitnessfunctions/manageClasses.png and /dev/null differ diff --git a/images/fitnessfunctions/manageClasses.webp b/images/fitnessfunctions/manageClasses.webp new file mode 100644 index 0000000..d67e0eb Binary files /dev/null and b/images/fitnessfunctions/manageClasses.webp differ diff --git a/images/fitnessfunctions/manageInstructors.png b/images/fitnessfunctions/manageInstructors.png deleted file mode 100644 index 290bac4..0000000 Binary files a/images/fitnessfunctions/manageInstructors.png and /dev/null differ diff --git a/images/fitnessfunctions/manageInstructors.webp b/images/fitnessfunctions/manageInstructors.webp new file mode 100644 index 0000000..3a18f4e Binary files /dev/null and b/images/fitnessfunctions/manageInstructors.webp differ diff --git a/images/fitnessfunctions/memberDashboard.png b/images/fitnessfunctions/memberDashboard.png deleted file mode 100644 index 87dde57..0000000 Binary files a/images/fitnessfunctions/memberDashboard.png and /dev/null differ diff --git a/images/fitnessfunctions/memberDashboard.webp b/images/fitnessfunctions/memberDashboard.webp new file mode 100644 index 0000000..2a62e75 Binary files /dev/null and b/images/fitnessfunctions/memberDashboard.webp differ diff --git a/images/fitnessfunctions/myClassess.png b/images/fitnessfunctions/myClassess.png deleted file mode 100644 index eec2d79..0000000 Binary files a/images/fitnessfunctions/myClassess.png and /dev/null differ diff --git a/images/fitnessfunctions/myClassess.webp b/images/fitnessfunctions/myClassess.webp new file mode 100644 index 0000000..06f50f1 Binary files /dev/null and b/images/fitnessfunctions/myClassess.webp differ diff --git a/images/fitnessfunctions/validationCheck.png b/images/fitnessfunctions/validationCheck.png deleted file mode 100644 index 1a44c63..0000000 Binary files a/images/fitnessfunctions/validationCheck.png and /dev/null differ diff --git a/images/fitnessfunctions/validationCheck.webp b/images/fitnessfunctions/validationCheck.webp new file mode 100644 index 0000000..040e8e3 Binary files /dev/null and b/images/fitnessfunctions/validationCheck.webp differ diff --git a/images/gorilla-home-page.png b/images/gorilla-home-page.png deleted file mode 100644 index f0730aa..0000000 Binary files a/images/gorilla-home-page.png and /dev/null differ diff --git a/images/gorilla-home-page.webp b/images/gorilla-home-page.webp new file mode 100644 index 0000000..e4edfde Binary files /dev/null and b/images/gorilla-home-page.webp differ diff --git a/images/pocketprofessors.png b/images/pocketprofessors.png deleted file mode 100644 index d8ba661..0000000 Binary files a/images/pocketprofessors.png and /dev/null differ diff --git a/images/pocketprofessors.webp b/images/pocketprofessors.webp new file mode 100644 index 0000000..67ee37e Binary files /dev/null and b/images/pocketprofessors.webp differ diff --git a/images/professorpannel.png b/images/professorpannel.png deleted file mode 100644 index b4b8660..0000000 Binary files a/images/professorpannel.png and /dev/null differ diff --git a/images/professorpannel.webp b/images/professorpannel.webp new file mode 100644 index 0000000..3997557 Binary files /dev/null and b/images/professorpannel.webp differ diff --git a/images/profile.jpg b/images/profile.jpg deleted file mode 100644 index 0855411..0000000 Binary files a/images/profile.jpg and /dev/null differ diff --git a/images/profile.webp b/images/profile.webp new file mode 100644 index 0000000..b52d96b Binary files /dev/null and b/images/profile.webp differ diff --git a/images/titlecardforpp.png b/images/titlecardforpp.png deleted file mode 100644 index 2129856..0000000 Binary files a/images/titlecardforpp.png and /dev/null differ diff --git a/images/titlecardforpp.webp b/images/titlecardforpp.webp new file mode 100644 index 0000000..39fe960 Binary files /dev/null and b/images/titlecardforpp.webp differ diff --git a/index.html b/index.html index a1d999c..0947fa0 100644 --- a/index.html +++ b/index.html @@ -6,26 +6,106 @@ - - - - - + + + + + + + +
Professional headshot of David Gusmao
@@ -42,14 +122,6 @@

David Gusmao

-
@@ -163,33 +235,6 @@

Certifications

GitHub · LinkedIn

-

Back to Resume

- - diff --git a/main.js b/main.js new file mode 100644 index 0000000..030df40 --- /dev/null +++ b/main.js @@ -0,0 +1,93 @@ +/* Shared behavior for all pages. + Loaded with `defer`, so the DOM is ready when this runs. + + Note: the initial light/dark choice is applied by a tiny inline script in + each , not here. It has to run before the first paint or the page + flashes dark before switching to light. */ +(function () { + 'use strict'; + + var root = document.documentElement; + + // Current year in the footer. + var yearEl = document.getElementById('year'); + if (yearEl) { + yearEl.textContent = new Date().getFullYear(); + } + + /* ---- Light / dark switch ---- */ + var toggle = document.querySelector('.navbar-toggle'); + if (toggle) { + var label = toggle.querySelector('.navbar-label'); + + // The button advertises what it will switch TO, not the current mode. + function syncToggle() { + var isLight = root.dataset.mode === 'light'; + toggle.setAttribute('aria-pressed', isLight ? 'true' : 'false'); + toggle.setAttribute('aria-label', isLight ? 'Switch to dark mode' : 'Switch to light mode'); + if (label) label.textContent = isLight ? 'Dark' : 'Light'; + } + + syncToggle(); + + toggle.addEventListener('click', function () { + root.dataset.mode = root.dataset.mode === 'light' ? 'dark' : 'light'; + try { localStorage.setItem('mode', root.dataset.mode); } catch (e) { /* private mode */ } + syncToggle(); + }); + } + + /* ---- Nav bar collapse ---- + Scrolling down shrinks the bar to icons only; scrolling back up restores + the labels. Runs regardless of prefers-reduced-motion because it is + navigation, not decoration — the stylesheet drops the transition instead. */ + var navbar = document.querySelector('.navbar'); + if (navbar) { + var lastY = window.scrollY; + var pending = false; + var DEADZONE = 6; // ignore sub-pixel jitter and rubber-banding + var TOP_ZONE = 48; // always expanded near the top of the page + + function syncNavbar() { + var y = window.scrollY; + + if (y < TOP_ZONE) { + navbar.classList.remove('is-collapsed'); + } else if (y > lastY + DEADZONE) { + navbar.classList.add('is-collapsed'); + } else if (y < lastY - DEADZONE) { + navbar.classList.remove('is-collapsed'); + } + + lastY = y; + pending = false; + } + + window.addEventListener('scroll', function () { + if (!pending) { + pending = true; + requestAnimationFrame(syncNavbar); + } + }, { passive: true }); + } + + /* ---- Cursor-tracked glow ---- + The .mouse-glow layer reads --mx/--my. */ + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; + + var raf = 0; + var x = 0; + var y = 0; + + function apply() { + root.style.setProperty('--mx', x + 'px'); + root.style.setProperty('--my', y + 'px'); + raf = 0; + } + + window.addEventListener('mousemove', function (e) { + x = e.clientX; + y = e.clientY; + if (!raf) raf = requestAnimationFrame(apply); + }, { passive: true }); +})(); diff --git a/project-contact-manager.html b/project-contact-manager.html index 4c95184..e997eae 100644 --- a/project-contact-manager.html +++ b/project-contact-manager.html @@ -5,20 +5,71 @@ Contact Manager — Project - - - - - + + + + + + + +

Contact Manager

@@ -35,11 +86,12 @@

Contact Manager

- CS Majors vs Gorilla — main home page + CS Majors vs Gorilla — main home page
Main Home Page is all that is accessable due to website being down.
-

Overview

+

Overview

A clean, semantic, and responsive contact manager built as a small-group project. The interface supports searching, adding, editing, and deleting contacts. The front end uses @@ -47,7 +99,7 @@

Overview

and updates the page without reloads.

-

My Role — Project Manager

+

My Role — Project Manager

  • Planning & scope: defined functional requirements, split milestones (search → add/edit → delete → polish), tracked progress using a Gantt chart.
  • Coordination: scheduled group meetings, tracked members, assigned issues/PRs, and enforced code-review checklists.
  • @@ -56,7 +108,7 @@

    My Role — Project Manager

  • DevOps: coordinated hosting and transfers (GoDaddy/DigitalOcean), SFTP/SSH (FileZilla, PuTTY), and environment setup documentation.
-

Features

+

Features

  • Live search: debounced input calls search.php and renders results dynamically.
  • CRUD: create/update/delete via PHP endpoints with optimistic UI and error fallback.
  • @@ -65,7 +117,7 @@

    Features

  • Accessible UI: proper labels/ids, descriptive buttons, visible focus states, logical tab order.
-

Architecture

+

Architecture

  • Front-end: Semantic HTML sections, CSS (flex/grid), unobtrusive JS for DOM updates.
  • Transport: XMLHttpRequest with JSON payloads/responses; centralized request helpers.
  • @@ -73,14 +125,14 @@

    Architecture

  • Data: MySQL schema for contacts (name, email, phone, etc.), parameterized queries.
-

Technical Contributions

+

Technical Contributions

  • Implemented the delete flow and fixed a null-DOM reference bug by guarding queries and deferring lookups until content mount.
  • Wrote XHR utilities for GET/POST, standardized success/error handling, and added status toasts.
  • Authored the form accessibility pattern (labels, error regions, focus management) and responsive form grid.
-

Challenges & Lessons

+

Challenges & Lessons

  • Race conditions: solved null element references with safe selectors and render timing.
  • Validation UX: combined native constraints with concise custom messages for clarity and screen-reader compatibility.
  • @@ -88,7 +140,7 @@

    Challenges & Lessons

  • Collaboration: Learned to manage a team, delegate tasks, and enforce quality through reviews and checklists.
-

Skills Demonstrated

+

Skills Demonstrated

  • Project management (scope, milestones, issue/PR workflow, stand-ups)
  • Semantic HTML & accessible forms (labels, aria-live, focus order)
  • @@ -97,7 +149,7 @@

    Skills Demonstrated

  • Basic DevOps (GoDaddy/DigitalOcean, FileZilla, PuTTY)
-

Links

+
  • Source Code: @@ -123,53 +175,5 @@

    Links

    Back to Resume

    - - - - diff --git a/project-pocketprofessors.html b/project-pocketprofessors.html index c1e8c30..dcc5859 100644 --- a/project-pocketprofessors.html +++ b/project-pocketprofessors.html @@ -5,20 +5,67 @@ PocketProfessors — Project - - - - - + + + + + + + +

    PocketProfessors

    @@ -32,17 +79,18 @@

    PocketProfessors

    - PocketProfessors landing + PocketProfessors landing
    -

    Overview

    +

    Overview

    PocketProfessors is a full-stack web application where users log in, open card packs, and collect hand-drawn cards of professors and people at UCF (including special developer cards). It’s built to feel like a playful TCG while staying fast, responsive, and accessible.

    -

    Key Features

    +

    Key Features

    • User authentication with JWT (login/session).
    • MongoDB integration for card data and collections.
    • @@ -51,7 +99,7 @@

      Key Features

    • Responsive design across web and mobile.
    -

    Technologies

    +

    Technologies

    • Frontend: React, TypeScript, Vite, CSS.
    • Backend: Node.js, Express.js.
    • @@ -62,7 +110,7 @@

      Technologies

    • Deployment: Nginx (Linux), GitHub.
    -

    How It Works

    +

    How It Works

    1. Sign in with JWT auth.
    2. Open packs to receive hand-drawn UCF/professor cards (with special dev cards).
    3. @@ -70,15 +118,17 @@

      How It Works

    - PocketProfessors pack opening + PocketProfessors pack opening
    Open weighted packs.
    - PocketProfessors collection views + PocketProfessors collection views
    Collect professors, track progress.
    -

    My Role & Contributions

    +

    My Role & Contributions

    Project Manager & Frontend Developer

    • Name & Website Theming, All Cards, All Art, Powerpoint, Gantt Chart, Recourse Allocation, Droplet, and Domain.
    • @@ -87,7 +137,7 @@

      Project Manager & Frontend Developer

    • Helped align component structure with React + TypeScript best practices.
    -

    Credits & Links

    +

    Credits & Links

    • Team project for UCF COP4331 (Group 25).
    • Live site: @@ -98,7 +148,7 @@

      Credits & Links

    -

    Challenges & Lessons

    +

    Challenges & Lessons

    • Collaboration: Learned to manage a team, delegate tasks, and enforce quality through reviews and checklists.
    • JWT lifecycle: Implemented token refresh and secure storage to balance UX and security.
    • @@ -138,30 +188,5 @@

      Challenges & Lessons

      Back to Resume

      - - diff --git a/styles.css b/styles.css index 21537f2..11212ac 100644 --- a/styles.css +++ b/styles.css @@ -2,57 +2,136 @@ Base / Reset ========================= */ * { box-sizing: border-box; } -html { color-scheme: dark; scroll-behavior: smooth; } +html { scroll-behavior: smooth; } @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } } +/* ========================= + Design tokens — DARK (default) + + Anything derived from --accent lives in the body.theme-* blocks below, + not here. A custom property that references another one resolves where it + is DECLARED, so `--x: var(--accent)` written at :root would freeze to the + :root accent and ignore the per-page theme. +========================= */ :root{ + color-scheme: dark; + --bg: #000; - --panel: rgba(10, 12, 14, .72); - --panel-2: rgba(10, 12, 14, .55); - --border: rgba(255,255,255,.08); + --panel: rgba(10, 12, 10, .72); + --panel-2: rgba(10, 12, 10, .55); --text: #d7d7d7; --muted: #9aa09a; - /* Accent system (default green) */ - --accent: #00ff00; - --neon: var(--accent); - - /* per-theme “glow” vars (overridden by theme classes) */ - --neon-soft: rgba(0,255,0,.55); - --neon-faint: rgba(0,255,0,.25); - - /* mouse pos injected via JS */ + /* neutral hairlines, sunken surfaces, drop shadows */ + --hair: rgba(255,255,255,.13); + --sunken: rgba(0,0,0,.35); + --code-bg: rgba(0,0,0,.55); + --img-bg: rgba(0,0,0,.6); + --shadow-drop: rgba(0,0,0,.55); + + /* background texture layers */ + --grid-line: rgba(255,255,255,.045); + --wash-1: rgba(255,255,255,.06); + --wash-2: rgba(255,255,255,.02); + --vig-inner: rgba(0,0,0,.75); + --vig-a: rgba(0,0,0,.15); + --vig-b: rgba(0,0,0,.9); + + /* glass panels */ + --glass-1: rgba(14,18,20,.72); + --glass-2: rgba(14,18,20,.46); + --glass-border: rgba(255,255,255,.12); + + /* nav bar height, expanded and collapsed */ + --bar-h: 84px; + --bar-h-min: 46px; + + /* mouse position, injected by main.js */ --mx: 50vw; --my: 35vh; } /* ========================= - Theme overrides (ONLY color) + Design tokens — LIGHT +========================= */ +:root[data-mode="light"]{ + color-scheme: light; + + --bg: #eef1ee; + --panel: rgba(255,255,255,.78); + --panel-2: rgba(255,255,255,.62); + + --text: #161a16; + --muted: #4c5450; + + --hair: rgba(0,0,0,.14); + --sunken: rgba(0,0,0,.05); + --code-bg: rgba(0,0,0,.07); + --img-bg: rgba(0,0,0,.04); + --shadow-drop: rgba(0,0,0,.14); + + --grid-line: rgba(0,0,0,.055); + --wash-1: rgba(0,0,0,.035); + --wash-2: rgba(0,0,0,.015); + --vig-inner: rgba(0,0,0,.06); + --vig-a: rgba(0,0,0,.02); + --vig-b: rgba(0,0,0,.10); + + --glass-1: rgba(255,255,255,.84); + --glass-2: rgba(255,255,255,.62); + --glass-border: rgba(0,0,0,.12); +} + +/* ========================= + Themes (accent colour only) + + --accent the glow / fill colour, used for borders, shadows, the bar + --accent-ink the same colour as TEXT. On light backgrounds the neon + accents sit around 1.3:1, so links and headings switch to a + darkened version that clears WCAG AA. ========================= */ body.theme-green { --accent: #00ff00; - --neon-soft: rgba(0,255,0,.55); - --neon-faint: rgba(0,255,0,.25); + --accent-soft: rgba(0,255,0,.55); + --accent-faint: rgba(0,255,0,.25); + --accent-ink: var(--accent); + --border: color-mix(in srgb, var(--accent) 12%, transparent); } body.theme-aqua { --accent: #2efcff; - --neon-soft: rgba(46,252,255,.55); - --neon-faint: rgba(46,252,255,.25); + --accent-soft: rgba(46,252,255,.55); + --accent-faint: rgba(46,252,255,.25); + --accent-ink: var(--accent); + --border: color-mix(in srgb, var(--accent) 12%, transparent); } body.theme-gold { --accent: #ffd54a; - --neon-soft: rgba(255,213,74,.55); - --neon-faint: rgba(255,213,74,.25); + --accent-soft: rgba(255,213,74,.55); + --accent-faint: rgba(255,213,74,.25); + --accent-ink: var(--accent); + --border: color-mix(in srgb, var(--accent) 12%, transparent); +} + +/* Readable ink + a stronger border once the page goes light. */ +:root[data-mode="light"] body.theme-green { --accent-ink: #0a7d18; } +:root[data-mode="light"] body.theme-aqua { --accent-ink: #0b6f75; } +:root[data-mode="light"] body.theme-gold { --accent-ink: #7a5600; } + +:root[data-mode="light"] body.theme-green, +:root[data-mode="light"] body.theme-aqua, +:root[data-mode="light"] body.theme-gold { + --border: color-mix(in srgb, var(--accent-ink) 30%, transparent); } /* ========================= - Body / Background “flow” + Body / background layers ========================= */ body{ margin: 0; + padding-top: var(--bar-h); /* clear the fixed nav bar */ font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; line-height: 1.6; color: var(--text); @@ -61,36 +140,34 @@ body{ overflow-x: hidden; } -/* grid + vignette layer */ +/* grid + top wash */ body::before{ content:""; position: fixed; inset: 0; pointer-events: none; z-index: -3; - - /* subtle grid + slight gradient */ background: - radial-gradient(1200px 700px at 30% 10%, rgba(255,255,255,.06), transparent 55%), - linear-gradient(to bottom, rgba(255,255,255,.02), transparent 30%), + radial-gradient(1200px 700px at 30% 10%, var(--wash-1), transparent 55%), + linear-gradient(to bottom, var(--wash-2), transparent 30%), repeating-linear-gradient( to right, - rgba(255,255,255,.045) 0, - rgba(255,255,255,.045) 1px, + var(--grid-line) 0, + var(--grid-line) 1px, transparent 1px, transparent 46px ), repeating-linear-gradient( to bottom, - rgba(255,255,255,.045) 0, - rgba(255,255,255,.045) 1px, + var(--grid-line) 0, + var(--grid-line) 1px, transparent 1px, transparent 46px ); opacity: .22; - filter: blur(.0px); } +/* vignette */ body::after{ content:""; position: fixed; @@ -98,11 +175,11 @@ body::after{ pointer-events: none; z-index: -2; background: - radial-gradient(1200px 900px at 50% 20%, transparent 35%, rgba(0,0,0,.75) 80%), - radial-gradient(900px 600px at 70% 85%, rgba(0,0,0,.15), rgba(0,0,0,.9)); + radial-gradient(1200px 900px at 50% 20%, transparent 35%, var(--vig-inner) 80%), + radial-gradient(900px 600px at 70% 85%, var(--vig-a), var(--vig-b)); } -/* mouse glow “halo” */ +/* cursor-tracked halo */ .mouse-glow{ position: fixed; inset: 0; @@ -114,7 +191,7 @@ body::after{ transparent 62% ), radial-gradient(520px 520px at var(--mx) var(--my), - var(--neon-faint), + var(--accent-faint), transparent 72% ); opacity: .95; @@ -122,31 +199,58 @@ body::after{ transition: opacity .25s ease; } +/* `screen` only ever brightens, so it does nothing on a near-white page. + Light mode multiplies a darker halo instead. */ +:root[data-mode="light"] .mouse-glow{ + background: + radial-gradient(260px 260px at var(--mx) var(--my), + rgba(0,0,0,.05), + transparent 62% + ), + radial-gradient(520px 520px at var(--mx) var(--my), + var(--accent-soft), + transparent 72% + ); + mix-blend-mode: multiply; + opacity: .55; +} + @media (prefers-reduced-motion: reduce){ .mouse-glow{ display:none; } } /* ========================= - Links / Headings + Links / headings ========================= */ -a { color: var(--neon); text-decoration: none; } +a { color: var(--accent-ink); text-decoration: none; } a:hover, a:focus-visible { text-decoration: underline; } -h1, h2{ - text-shadow: 0 0 10px var(--neon-faint); -} +h1, h2{ text-shadow: 0 0 10px var(--accent-faint); } h2 { margin-top: 0; } h3 { margin: 0 0 .25rem; } +.neon{ + color: var(--accent-ink); + text-shadow: 0 0 8px var(--accent-soft), 0 0 16px var(--accent-faint); +} + +.subtitle{ + margin: 0; + color: var(--text); + opacity: .9; +} +.subtitle strong{ color: var(--accent-ink); } + /* Accessible skip link */ .skip-link { position: absolute; left: -999px; top: -999px; } .skip-link:focus { - left: 12px; top: 12px; + left: 12px; top: calc(var(--bar-h) + 12px); + z-index: 60; padding: .55rem .85rem; - background: var(--neon); + background: var(--accent); color: #000; border-radius: 10px; - box-shadow: 0 0 14px var(--neon-soft); + box-shadow: 0 0 14px var(--accent-soft); } /* ========================= @@ -160,48 +264,171 @@ section, article{ border: 1px solid var(--border); border-radius: 18px; padding: 1rem 1.05rem; - box-shadow: - 0 0 0 transparent, - inset 0 0 0 transparent; transition: box-shadow .2s ease, transform .08s ease, border-color .2s ease; } +/* Panels glow slightly weaker than the header */ section:hover, article:hover{ - border-color: rgba(255,255,255,.12); + border-color: var(--hair); box-shadow: - 0 0 14px var(--neon-faint), - 0 0 28px rgba(0,0,0,.55); + 0 0 16px var(--accent-faint), + 0 0 32px var(--shadow-drop); } -/* target highlight when navigating via top buttons */ +/* highlight the section jumped to from the nav bar */ :target{ - scroll-margin-top: 12px; - outline: 2px solid var(--neon); + scroll-margin-top: calc(var(--bar-h) + 12px); + outline: 2px solid var(--accent-ink); outline-offset: 6px; border-radius: 18px; animation: target-pop .9s ease 1; } @keyframes target-pop{ - 0% { box-shadow: 0 0 0 var(--neon-faint); } - 40% { box-shadow: 0 0 18px var(--neon-soft); } - 100% { box-shadow: 0 0 0 var(--neon-faint); } + 0% { box-shadow: 0 0 0 var(--accent-faint); } + 40% { box-shadow: 0 0 18px var(--accent-soft); } + 100% { box-shadow: 0 0 0 var(--accent-faint); } } @media (prefers-reduced-motion: reduce){ :target{ animation: none; } } +/* The "Top" link points at an empty marker; it should not draw a highlight. */ +#top{ display: block; height: 0; } +#top:target{ outline: none; animation: none; } + +/* ========================= + Glass surface +========================= */ +.glass{ + background: linear-gradient(180deg, var(--glass-1), var(--glass-2)); + border: 1px solid var(--glass-border); + border-radius: 24px; + backdrop-filter: blur(10px); + box-shadow: + 0 18px 50px var(--shadow-drop), + 0 0 0 1px rgba(0,0,0,0.2); + transition: box-shadow 220ms ease, border-color 220ms ease; +} + +/* ========================= + Nav bar + Accent-filled bar across the top with black lettering. Icon above label; + collapses to small icons only while scrolling down. +========================= */ +.navbar{ + position: fixed; + top: 0; + left: 0; + right: 0; + height: var(--bar-h); + z-index: 50; + + display: flex; + align-items: stretch; + padding: 0 .5rem; + + background: var(--accent); + color: #000; + box-shadow: 0 0 18px var(--accent-faint); + transition: height .22s ease, box-shadow .25s ease; +} + +/* The whole bar lifts when the cursor is anywhere over it. */ +.navbar:hover, +.navbar:focus-within{ + box-shadow: + 0 0 34px var(--accent-soft), + 0 0 72px var(--accent-faint); +} + +/* Only the links scroll; the switch sits outside so it is always reachable. */ +.navbar-scroll{ + flex: 1 1 auto; + display: flex; + align-items: center; + justify-content: space-evenly; + gap: .2rem; + min-width: 0; + overflow-x: auto; + overflow-y: hidden; + overscroll-behavior-x: contain; +} + +.navbar-item{ + flex: 0 0 auto; + display: grid; + justify-items: center; + align-content: center; + gap: .25rem; + + padding: .4rem .55rem; + border-radius: 12px; + + /* the bar is already accent-filled, so ink is black in both modes */ + color: #000; + font-size: .8rem; + font-weight: 700; + line-height: 1.15; + text-align: center; + white-space: nowrap; + transition: background-color .18s ease, transform .12s ease; +} + +.navbar-item:hover, +.navbar-item:focus-visible{ + background: rgba(0,0,0,.16); + text-decoration: none; +} +.navbar-item:focus-visible{ + outline: 2px solid #000; + outline-offset: -2px; +} +.navbar-item:active{ transform: scale(.96); } + +.navbar-item svg{ + display: block; + width: 30px; + height: 30px; + transition: width .22s ease, height .22s ease; +} + +/* Scrolled down: small icons only. */ +.navbar.is-collapsed{ height: var(--bar-h-min); } +.navbar.is-collapsed .navbar-label{ display: none; } +.navbar.is-collapsed .navbar-item{ padding: .3rem .5rem; } +.navbar.is-collapsed .navbar-item svg{ width: 21px; height: 21px; } + +/* Light / dark switch, set slightly apart at the end of the bar. */ +.navbar-toggle{ + flex: 0 0 auto; + align-self: center; + appearance: none; + border: 0; + border-left: 1px solid rgba(0,0,0,.22); + border-radius: 0; + padding-left: .8rem; + margin-left: .4rem; + background: transparent; + font: inherit; + cursor: pointer; +} +/* Only one of the two icons shows, depending on the active mode. */ +.navbar-toggle .icon-dark{ display: none; } +:root[data-mode="light"] .navbar-toggle .icon-dark{ display: block; } +:root[data-mode="light"] .navbar-toggle .icon-light{ display: none; } + +@media (prefers-reduced-motion: reduce){ + .navbar, .navbar-item, .navbar-item svg{ transition: none; } +} + /* ========================= - Header / Nav + Page header ========================= */ header{ - background: rgba(0,0,0,.75); - color: var(--neon); - padding: 1rem; - border: 1px solid var(--neon); - border-radius: 22px; - box-shadow: 0 0 14px var(--neon-faint), inset 0 0 10px var(--neon-faint); max-width: 960px; margin: 1rem auto 0; + padding: 1rem; + color: var(--accent-ink); } header .container{ @@ -215,66 +442,47 @@ header h1{ font-size: clamp(1.25rem, 3.5vw, 2rem); } -header address a{ color: var(--neon); } -header address a:hover{ text-decoration: underline; } +header address a{ color: var(--accent-ink); } header.simple{ display:grid; gap: .45rem; } -.neon{ - color: var(--neon); - text-shadow: 0 0 8px var(--neon-soft), 0 0 16px var(--neon-faint); +/* Header reads hotter than the panels below it */ +header.glass{ + border-color: color-mix(in srgb, var(--accent) 55%, transparent); + box-shadow: + 0 0 18px var(--accent-faint), + inset 0 0 12px var(--accent-faint); + outline-color: var(--accent); } -.subtitle{ - margin: 0; - color: var(--text); - opacity: .9; +header.glass:hover{ + border-color: var(--accent); + box-shadow: + 0 0 26px var(--accent-soft), + 0 0 60px var(--accent-faint), + inset 0 0 16px var(--accent-faint); } -/* top nav buttons */ -.topnav{ - margin: .75rem auto 0; - display:flex; - flex-wrap: wrap; - gap: .55rem .7rem; - justify-content: center; - padding-bottom: .25rem; +header.glass:hover h1, +header.glass:hover .neon{ + text-shadow: + 0 0 34px var(--accent-soft), + 0 0 14px var(--accent-faint); } -.btn{ - appearance:none; - display:inline-block; - padding: .55rem .95rem; - border-radius: 999px; - border: 1px solid var(--neon); - background: rgba(0,0,0,.65); - color: var(--neon); - text-decoration:none; - font-weight: 750; - letter-spacing: .2px; - box-shadow: 0 0 0 transparent; - transition: box-shadow .18s ease, transform .06s ease, background-color .18s ease; -} -.btn:hover, .btn:focus-visible{ - outline: none; - background: rgba(0,0,0,.85); - box-shadow: 0 0 10px var(--neon-soft), 0 0 22px var(--neon-faint); -} -.btn:active{ transform: translateY(1px) scale(.985); } - /* ========================= - Avatar / Profile + Avatar / profile ========================= */ .avatar{ width: clamp(86px, 14vw, 140px); aspect-ratio: 1 / 1; border-radius: 50%; overflow: hidden; - border: 2px solid var(--neon-faint); - box-shadow: 0 2px 10px rgba(0,0,0,.6); + border: 2px solid var(--accent-faint); + box-shadow: 0 2px 10px var(--shadow-drop); transition: box-shadow .2s ease, border-color .2s ease; } @@ -289,19 +497,17 @@ header.simple{ } .avatar:hover{ - border-color: var(--neon); - box-shadow: 0 0 10px var(--neon-soft), 0 0 28px var(--neon-faint); + border-color: var(--accent); + box-shadow: 0 0 10px var(--accent-soft), 0 0 28px var(--accent-faint); } .avatar:hover .profile{ - filter: drop-shadow(0 0 4px var(--neon-faint)); + filter: drop-shadow(0 0 4px var(--accent-faint)); } /* ========================= - Lists / Tags / Projects + Lists / tags / projects ========================= */ .project-links{ padding-left: 1.25rem; } -.project-links a{ text-decoration: none; } -.project-links a:hover{ text-decoration: underline; } .tags{ display:flex; @@ -312,17 +518,16 @@ header.simple{ margin: .5rem 0 0; } .tags li{ - border: 1px solid rgba(255,255,255,.14); + border: 1px solid var(--hair); border-radius: 999px; padding: .28rem .6rem; font-size: .95rem; color: var(--text); background: var(--panel-2); - box-shadow: 0 0 10px rgba(0,0,0,.35); } /* ========================= - Figures / Images + Figures / images ========================= */ figure{ margin: 0; } figure + figure{ margin-top: .95rem; } @@ -330,16 +535,15 @@ figure + figure{ margin-top: .95rem; } figure img{ width: 100%; height: auto; - border: 1px solid rgba(255,255,255,.10); + border: 1px solid var(--hair); border-radius: 14px; - background: rgba(0,0,0,.6); - box-shadow: 0 0 0 transparent; + background: var(--img-bg); transition: box-shadow .2s ease, border-color .2s ease, transform .08s ease; } figure:hover img{ - border-color: rgba(255,255,255,.16); - box-shadow: 0 0 16px var(--neon-faint); + border-color: var(--border); + box-shadow: 0 0 16px var(--accent-faint); } figcaption{ @@ -352,8 +556,8 @@ figcaption{ Details / code blocks ========================= */ details{ - background: rgba(0,0,0,.35); - border: 1px solid rgba(255,255,255,.10); + background: var(--sunken); + border: 1px solid var(--hair); border-radius: 14px; padding: .75rem .9rem; } @@ -365,8 +569,8 @@ pre{ overflow:auto; border-radius: 12px; padding: .75rem; - background: rgba(0,0,0,.55); - border: 1px solid rgba(255,255,255,.10); + background: var(--code-bg); + border: 1px solid var(--hair); } /* ========================= @@ -375,186 +579,38 @@ pre{ footer{ max-width: 960px; margin: 1rem auto 2rem; - border-top: 1px solid rgba(255,255,255,.08); + border-top: 1px solid var(--hair); padding: 1rem; text-align:center; color: var(--muted); } -/* Shared glass container (header + sections) */ -.glass{ - background: linear-gradient(180deg, rgba(14,18,20,0.72), rgba(14,18,20,0.46)); - border: 1px solid rgba(255,255,255,0.12); - border-radius: 24px; - backdrop-filter: blur(10px); - box-shadow: - 0 18px 50px rgba(0,0,0,0.55), - 0 0 0 1px rgba(0,0,0,0.2); - transition: box-shadow 220ms ease, border-color 220ms ease; -} +/* ========================= + Responsive +========================= */ +@media (max-width: 760px){ + :root{ --bar-h: 74px; --bar-h-min: 42px; } -/* Glow interaction — THIS is what you were missing */ -.glass:hover, -.panel:hover{ - border-color: color-mix(in oklab, var(--accent) 45%, rgba(255,255,255,0.12)); - box-shadow: - 0 22px 70px rgba(0,0,0,0.65), - 0 0 40px var(--glow); -} + .navbar-scroll{ + justify-content: flex-start; + gap: .05rem; + } + .navbar-item{ + padding: .3rem .34rem; + font-size: .64rem; + } + .navbar-item svg{ width: 25px; height: 25px; } + .navbar.is-collapsed .navbar-item svg{ width: 19px; height: 19px; } -/* Stronger header hover (requested) */ -header.glass:hover h1, -header.glass:hover .neon{ - text-shadow: - 0 0 26px var(--glow), - 0 0 4px rgba(255,255,255,0.25); + .navbar-toggle{ + padding-left: .5rem; + margin-left: .25rem; + } } - -/* ========================= - Responsive tweaks -========================= */ @media (max-width: 640px){ header .container{ flex-direction: column; align-items: flex-start; } - .topnav{ justify-content: flex-start; } -} - -/* ========================= - STRONGER HEADER GLOW - ========================= */ - -.project-header { - position: relative; - border-radius: 18px; - box-shadow: - 0 0 14px rgba(0, 255, 180, 0.35), - 0 0 30px rgba(0, 255, 180, 0.22); - transition: box-shadow 0.35s ease, transform 0.35s ease; -} - -.project-header:hover { - box-shadow: - 0 0 18px rgba(0, 255, 200, 0.55), - 0 0 45px rgba(0, 255, 200, 0.35), - 0 0 70px rgba(0, 255, 200, 0.18); - transform: translateY(-2px); -} - -/* ========================= - FITNESS FUNCTIONS COLORS - ========================= */ - -.fitness-functions, -.fitness-functions a, -.fitness-functions span, -.fitness-functions strong { - color: var(--fitness-accent, #3fffd6); -} - -.fitness-functions a:hover { - text-shadow: 0 0 8px rgba(63, 255, 214, 0.8); -} - -/* ========================= - POCKET PROFESSORS COLORS - ========================= */ - -.pocket-professors, -.pocket-professors a, -.pocket-professors span, -.pocket-professors strong { - color: var(--pocket-accent, #ffd84d); -} - -.pocket-professors a:hover { - text-shadow: 0 0 10px rgba(255, 216, 77, 0.9); -} - - -/* ========================================================= - FIX 1: define --glow so the existing glass hover works - (your .glass:hover rule uses var(--glow) but it wasn't defined) - ========================================================= */ -:root{ - --glow: var(--neon-soft); -} - -/* ========================================================= - FIX 2: Header glow stronger than section/article panels - Applies to your real header:
      - ========================================================= */ -header.glass{ - /* keep your current look, but ensure glow reads */ - box-shadow: - 0 0 18px var(--neon-faint), - inset 0 0 12px var(--neon-faint); -} - -header.glass:hover{ - border-color: var(--neon); - box-shadow: - 0 0 26px var(--neon-soft), - 0 0 60px var(--neon-faint), - inset 0 0 16px var(--neon-faint); -} - -/* Keep header text a bit hotter than panels on hover */ -header.glass:hover .neon, -header.glass:hover h1{ - text-shadow: - 0 0 34px var(--neon-soft), - 0 0 14px var(--neon-faint); -} - -/* ========================================================= - FIX 3: Panels stay slightly weaker than header - (your pages use
      for the big panels) - ========================================================= */ -article:hover, section:hover{ - box-shadow: - 0 0 16px var(--neon-faint), - 0 0 32px rgba(0,0,0,.55); -} - -/* ========================================================= - FIX 4: Remove "random green" by forcing theme color on accents - (no new classes needed; uses your existing body.theme-* classes) - ========================================================= */ -body.theme-aqua .neon, -body.theme-aqua a{ - color: var(--accent); -} - -body.theme-gold .neon, -body.theme-gold a{ - color: var(--accent); -} - -/* Optional: stop any inside subtitles from looking "extra green" */ -.subtitle strong{ - color: var(--accent); -} - -/* ========================================================= - REMOVE STRAY GREEN BORDER ON HEADERS - Force header border to use page accent ONLY - ========================================================= */ - -/* Override any leftover neon-green border */ -header.glass{ - border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent); -} - -/* Hover state — still stronger glow, but no green rim */ -header.glass:hover{ - border-color: var(--accent); -} - -/* Safety net: kill any hardcoded neon green borders */ -header.glass, -header.glass *{ - outline-color: var(--accent); }