Josh Bruce Online

GPT RS Dashboard — Standing Instructions

This is a static dashboard for the GPT RS F1 Fantasy 2026 team, designed to be hosted on GitHub Pages. It is a single-page HTML app with no build step, no framework, and no server-side code.

Purpose

Display the current GPT RS team composition (5 drivers + 2 constructors), prices, budget status, and upcoming race info in a dark-mode, F1-branded CoverFlow-style carousel that fits entirely within the viewport without scrolling.

Parent Project

This dashboard is a subproject of the F1 Fantasy automation system located at ../. The parent project’s CLAUDE.md governs the automated Claude agent that manages the GPT RS team — do not modify that file from within this dashboard project. However, you can and should read it for context about the team, data formats, and season structure.

Architecture

Single-file app

Everything lives in index.html — styles, markup, and JavaScript are all inline. There is no build step. Keep it this way. Do not split into separate CSS/JS files unless the user explicitly requests it.

Data flow

  1. On page load, the app attempts to fetch team and price JSON from GitHub raw URLs
  2. If the fetch fails (404, CORS, network error), it falls back to embedded JSON baked into the page
  3. The data is parsed and rendered into the DOM via renderTeam()

Remote data URLs

BASE: https://raw.githubusercontent.com/JoshCBruce/JoshCBruce.github.io/main/fantasy-data-26
Team: {BASE}/claude-team/team-PRE-ROUND-{N}.json
Prices: {BASE}/all-prices/prices-PRE-ROUND-{N}.json

Currently hardcoded to PRE-ROUND-1. As the season progresses, the TEAM_FILE and PRICES_FILE constants at the top of the <script> block must be updated to point to the latest round’s data, or the page should be made to discover the latest file automatically.

Fallback data

The FALLBACK_TEAM, FALLBACK_PREV_TEAM, and FALLBACK_PRICES constants contain full copies of the JSON. When updating for a new round, update both the remote file references AND the embedded fallback data. FALLBACK_PREV_TEAM represents the previous round and is always passed as the second slide in the carousel.

File Structure

dashboard/
  index.html          # The entire app — HTML, CSS, JS
  CLAUDE.md           # This file
  fonts/              # F1 and Titillium Web font files (TTF)
    Formula1-Regular.ttf
    Formula1-Bold.ttf
    Formula1-Wide.ttf
    Formula1-Black.ttf
    TitilliumWeb-Regular.ttf
    TitilliumWeb-SemiBold.ttf
    TitilliumWeb-Bold.ttf
    TitilliumWeb-Light.ttf

Data Schemas

Team JSON (team-PRE-ROUND-{N}.json)

{
  "metadata": {
    "scraped_at": "ISO8601",
    "round_context": {
      "last_completed_round": null | number,
      "upcoming_round": number,
      "upcoming_race": "string",
      "is_race_week": boolean,
      "file_type": "pre-round" | "post-round"
    }
  },
  "team": {
    "drivers": [
      {
        "name": "Full Name",
        "team": "Constructor Name",    // must match keys in TEAM_SLUGS
        "price_millions": 27.4,
        "price_change_millions": 0.0,
        "is_captain": true | false     // DRS Boost (2x) target
      }
    ],
    "constructors": [
      {
        "name": "Constructor Name",
        "price_millions": 23.3,
        "price_change_millions": 0.0
      }
    ],
    "total_team_value_millions": 99.9
  }
}

Prices JSON (prices-PRE-ROUND-{N}.json)

{
  "metadata": { /* same as team JSON */ },
  "drivers": [
    {
      "name": "Full Name",
      "team": "Constructor Name",
      "price_millions": 27.7,
      "ownership_pct": 28.0
    }
  ],
  "constructors": [
    {
      "name": "Constructor Name",
      "price_millions": 29.3,
      "ownership_pct": 23.0
    }
  ]
}

Critical: name matching

Driver/constructor names in the team JSON must exactly match the keys in DRIVER_CODES, TEAM_SLUGS, TEAM_COLORS, and TEAM_BG_RGB lookup objects. If F1 changes a team name (e.g. “Audi” becomes “Sauber”), all four maps must be updated.

F1 Media Image URLs

Driver and constructor images are loaded from Formula 1’s Cloudinary CDN. The URL patterns are:

Driver portraits

https://media.formula1.com/image/upload/ar_16:9,c_crop,g_north/b_rgb:{TEAM_BG}/c_fill,w_480/q_auto/f_webp/common/f1/2026/{TEAM_SLUG}/{DRIVER_CODE}/2026{TEAM_SLUG}{DRIVER_CODE}front.png

Constructor cars

https://media.formula1.com/image/upload/c_lpad,h_400,w_900/b_rgb:{TEAM_BG}/c_fill,w_600/q_auto/f_webp/common/f1/2026/{TEAM_SLUG}/2026{TEAM_SLUG}carright.png

Lookup tables

These are the complete 2026 season mappings. If a new driver joins mid-season, add their code here.

Team slugs (maps display name to URL path segment): | Display Name | Slug | |—|—| | Red Bull Racing | redbullracing | | Mercedes | mercedes | | Ferrari | ferrari | | McLaren | mclaren | | Aston Martin | astonmartin | | Alpine | alpine | | Williams | williams | | Racing Bulls | racingbulls | | Haas F1 Team | haasf1team | | Audi | audi | | Cadillac | cadillac |

Driver codes (maps full name to F1 media code): | Driver | Code | |—|—| | Max Verstappen | maxver01 | | George Russell | georus01 | | Lando Norris | lannor01 | | Oscar Piastri | oscpia01 | | Kimi Antonelli | andant01 | | Charles Leclerc | chalec01 | | Lewis Hamilton | lewham01 | | Isack Hadjar | isahad01 | | Pierre Gasly | piegas01 | | Carlos Sainz | carsai01 | | Alexander Albon | alealb01 | | Fernando Alonso | feralo01 | | Lance Stroll | lanstr01 | | Oliver Bearman | olibea01 | | Esteban Ocon | estoco01 | | Nico Hulkenberg | nichul01 | | Gabriel Bortoleto | gabbor01 | | Liam Lawson | lialaw01 | | Arvid Lindblad | arvlin01 | | Franco Colapinto | fracol01 | | Sergio Perez | serper01 | | Valtteri Bottas | valbot01 |

Team brand colors (hex, used for card color bars and image backgrounds): | Team | Color | BG RGB (no #) | |—|—|—| | Red Bull Racing | #3671C6 | 3671c6 | | Mercedes | #27F4D2 | 27f4d2 | | Ferrari | #E8002D | e8002d | | McLaren | #FF8000 | ff8000 | | Aston Martin | #229971 | 229971 | | Alpine | #00A1E8 | 00a1e8 | | Williams | #1868DB | 1868db | | Racing Bulls | #6692FF | 6692ff | | Haas F1 Team | #B6BABD | b6babd | | Audi | #FF2D00 | ff2d00 | | Cadillac | #AAAAAD | aaaaad |

Design System

Layout principles

The team viewer is a CoverFlow-style horizontal carousel:

Carousel constants (JS):

SLIDE_WIDTH  = 590   // px — matches 3×190px cards + 2×10px gaps
SLIDE_GAP    = 80    // px — between slides in the rail
SLIDE_STRIDE = 670   // px — SLIDE_WIDTH + SLIDE_GAP

Rail transform formula:

tx = -currentSlide * SLIDE_STRIDE + getActiveLeft()
// getActiveLeft() = Math.round(window.innerWidth * 0.22)

Vertical alignment

The two slides are vertically aligned so that the centre of the 3-wide (second) driver row is at the same y-coordinate across both slides. This row centre sits at y ≈ 279px from each slide’s top:

With scale(0.9) from transform-origin: top center, the inactive slide’s 3-wide row would fall at 0.9 × 279 = 251px. Adding translateY(31px) before scaling corrects this: y' = 0.9(y + 31) = 0.9y + 27.9, so y'(279) = 279. The alignment holds within <1px throughout the transition animation.

Card grid structure (within each 590px slide)

Visual identity

Typography

CSS variables

--bg-primary: #15151e
--bg-secondary: #1e1e2e
--bg-card: #252536
--text-primary: #ffffff
--text-secondary: #a0a0b8
--text-muted: #6b6b82
--accent: #e10600       /* F1 red */
--green: #00d27a        /* captain glow, positive price changes, budget remaining */
--red: #e10600          /* negative price changes */
--border: #33334a
--border-subtle: #2a2a3e

Development

Local testing

cd dashboard/
python3 -m http.server 8901
# Open http://localhost:8901/

Deployment

Copy the entire dashboard/ folder contents to the GitHub Pages repo root (or a subdirectory). No build step needed.

When updating for a new round

  1. Update TEAM_FILE and PRICES_FILE constants to point to the new round’s JSON filenames
  2. Update FALLBACK_TEAM and FALLBACK_PRICES with the new round’s data
  3. Move the old FALLBACK_TEAM data into FALLBACK_PREV_TEAM (it becomes the previous-round slide)
  4. If a driver was transferred, verify their name and team match the lookup tables
  5. If a mid-season driver swap occurred (e.g. reserve driver replaces a race driver), add the new driver’s code to DRIVER_CODES

If a constructor name changes

Update ALL of these maps: TEAM_SLUGS, TEAM_COLORS, TEAM_BG_RGB, and verify the team JSON uses the new name.

The carousel is designed for N slides. To add a third round slide (e.g. post-round history):

  1. Add the team data to a new FALLBACK_ROUND_N constant
  2. In renderTeam, append another <div class="team-slide is-inactive"> to the rail HTML
  3. The JS navigate() and applyCarousel() functions will handle it automatically — count is read from the DOM

Constraints and Gotchas

Future Enhancements (not yet implemented)