Created
April 20, 2026 18:39
-
-
Save xicubed/4d2ec3c9030bd0b1c76b386aa9e36278 to your computer and use it in GitHub Desktop.
Omarchy theme switcher in browser.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>Engineering Outreach Portal — Design Exploration</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600&display=swap" rel="stylesheet"> | |
| <style> | |
| /* ========================================================= | |
| GENERATEPRESS GLOBAL COLORS — the 7 canonical variables | |
| These are the ONLY color primitives the design touches. | |
| Every Omarchy theme swap just rewrites these 7 values. | |
| ========================================================= */ | |
| :root { | |
| /* Defaults = Omarchy "Futurism" palette | |
| (bjarneo/omarchy-futurism-theme colors.toml) */ | |
| --contrast: #F0F8FF; /* primary text (foreground) */ | |
| --contrast-2: #AFC3DA; /* secondary text */ | |
| --contrast-3: #5076B2; /* borders, placeholders, tertiary */ | |
| --base: #0A1428; /* page background */ | |
| --base-2: #0F1D3A; /* surface (cards, header) */ | |
| --base-3: #17294F; /* elevated / hover / input bg */ | |
| --accent: #FF40A3; /* primary interactive */ | |
| /* Extension vars — bonus slots. Not required for the GP mapping | |
| to work; we add them so the design can express more richness | |
| without breaking the core 7-slot contract. */ | |
| --accent-2: #00BFFF; /* secondary accent (e.g. info) */ | |
| --danger: #FF40A3; /* shares accent in Futurism */ | |
| --success: #00BFFF; | |
| /* Geometry + motion */ | |
| --radius-sm: 4px; | |
| --radius: 8px; | |
| --radius-lg: 14px; | |
| --shadow-1: 0 1px 0 color-mix(in oklab, var(--contrast) 6%, transparent); | |
| --shadow-2: 0 8px 32px color-mix(in oklab, #000 35%, transparent), | |
| 0 2px 6px color-mix(in oklab, #000 30%, transparent); | |
| --ease: cubic-bezier(.2,.7,.2,1); | |
| } | |
| /* Grayscale mode: desaturate everything on the fly. | |
| Applied by toggling [data-grayscale] on <html>. */ | |
| html[data-grayscale] { filter: grayscale(1); } | |
| /* ---------- Reset-ish ---------- */ | |
| *, *::before, *::after { box-sizing: border-box; } | |
| html, body { margin: 0; padding: 0; } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| font-size: 16px; | |
| line-height: 1.55; | |
| color: var(--contrast); | |
| background: var(--base); | |
| -webkit-font-smoothing: antialiased; | |
| min-height: 100vh; | |
| transition: background-color .25s var(--ease), color .25s var(--ease); | |
| } | |
| a { color: var(--accent); text-decoration: none; } | |
| a:hover { text-decoration: underline; text-underline-offset: 3px; } | |
| button { font-family: inherit; cursor: pointer; } | |
| h1, h2, h3, h4 { font-family: 'Fraunces', Georgia, serif; font-weight: 500; letter-spacing: -0.01em; margin: 0; } | |
| code, pre, .mono { font-family: 'JetBrains Mono', ui-monospace, monospace; } | |
| /* ========================================================= | |
| SHARED CHROME | |
| ========================================================= */ | |
| .site-header { | |
| position: sticky; top: 0; z-index: 40; | |
| backdrop-filter: blur(12px); | |
| background: color-mix(in oklab, var(--base-2) 85%, transparent); | |
| border-bottom: 1px solid color-mix(in oklab, var(--contrast-3) 35%, transparent); | |
| padding: 14px 28px; | |
| display: flex; align-items: center; gap: 24px; | |
| } | |
| .brand { | |
| display: flex; align-items: center; gap: 12px; font-weight: 600; | |
| color: var(--contrast); letter-spacing: -0.01em; | |
| } | |
| .brand-mark { | |
| width: 28px; height: 28px; border-radius: 6px; | |
| background: | |
| linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%); | |
| position: relative; | |
| display: grid; place-items: center; | |
| color: var(--base); | |
| font-family: 'JetBrains Mono', monospace; | |
| font-weight: 700; font-size: 13px; | |
| } | |
| .brand-name { font-size: 15px; } | |
| .brand-sub { color: var(--contrast-2); font-size: 12px; font-weight: 400; } | |
| .nav { display: flex; gap: 4px; margin-left: 16px; } | |
| .nav a { | |
| color: var(--contrast-2); font-size: 14px; font-weight: 500; | |
| padding: 6px 12px; border-radius: var(--radius-sm); | |
| } | |
| .nav a[aria-current="page"] { color: var(--contrast); background: color-mix(in oklab, var(--accent) 12%, transparent); } | |
| .nav a:hover { color: var(--contrast); text-decoration: none; background: color-mix(in oklab, var(--contrast) 6%, transparent); } | |
| .header-right { margin-left: auto; display: flex; align-items: center; gap: 12px; } | |
| .user-chip { | |
| display: flex; align-items: center; gap: 10px; | |
| padding: 6px 12px 6px 6px; | |
| background: var(--base-3); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 40%, transparent); | |
| border-radius: 999px; | |
| font-size: 13px; color: var(--contrast); | |
| } | |
| .avatar { | |
| width: 28px; height: 28px; border-radius: 50%; | |
| background: linear-gradient(135deg, var(--accent), var(--accent-2)); | |
| display: grid; place-items: center; | |
| color: var(--base); font-weight: 700; font-size: 12px; | |
| font-family: 'JetBrains Mono', monospace; | |
| } | |
| .site-footer { | |
| margin-top: 80px; | |
| border-top: 1px solid color-mix(in oklab, var(--contrast-3) 30%, transparent); | |
| padding: 32px 28px; | |
| color: var(--contrast-2); font-size: 13px; | |
| display: flex; justify-content: space-between; gap: 24px; flex-wrap: wrap; | |
| } | |
| .site-footer a { color: var(--contrast-2); } | |
| .site-footer .mono { font-size: 12px; } | |
| /* ========================================================= | |
| BUTTONS + INPUTS | |
| ========================================================= */ | |
| .btn { | |
| display: inline-flex; align-items: center; justify-content: center; gap: 8px; | |
| font-weight: 600; font-size: 14px; | |
| padding: 10px 18px; | |
| border-radius: var(--radius); | |
| border: 1px solid transparent; | |
| transition: transform .08s var(--ease), background-color .15s var(--ease), border-color .15s var(--ease); | |
| line-height: 1; | |
| } | |
| .btn:active { transform: translateY(1px); } | |
| .btn-primary { | |
| background: var(--accent); color: var(--base); | |
| border-color: var(--accent); | |
| } | |
| .btn-primary:hover { background: color-mix(in oklab, var(--accent) 85%, var(--contrast)); } | |
| .btn-ghost { | |
| background: transparent; color: var(--contrast); | |
| border-color: color-mix(in oklab, var(--contrast-3) 50%, transparent); | |
| } | |
| .btn-ghost:hover { background: color-mix(in oklab, var(--contrast) 8%, transparent); } | |
| .btn-block { width: 100%; } | |
| .field { display: grid; gap: 6px; } | |
| .field label { font-size: 13px; color: var(--contrast-2); font-weight: 500; } | |
| .input { | |
| width: 100%; | |
| background: var(--base-3); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 50%, transparent); | |
| color: var(--contrast); | |
| padding: 11px 14px; | |
| border-radius: var(--radius); | |
| font-size: 14px; | |
| font-family: inherit; | |
| outline: none; | |
| transition: border-color .15s var(--ease), box-shadow .15s var(--ease); | |
| } | |
| .input::placeholder { color: color-mix(in oklab, var(--contrast-3) 90%, var(--contrast-2)); } | |
| .input:focus { | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 25%, transparent); | |
| } | |
| .checkbox-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--contrast-2); } | |
| .checkbox-row input { accent-color: var(--accent); width: 16px; height: 16px; } | |
| .alert { | |
| padding: 12px 14px; | |
| border-radius: var(--radius); | |
| border: 1px solid color-mix(in oklab, var(--accent) 40%, transparent); | |
| background: color-mix(in oklab, var(--accent) 12%, transparent); | |
| color: var(--contrast); | |
| font-size: 13px; | |
| display: flex; gap: 10px; align-items: flex-start; | |
| } | |
| .alert::before { | |
| content: ""; width: 6px; height: 6px; border-radius: 50%; | |
| background: var(--accent); margin-top: 7px; flex: none; | |
| box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 30%, transparent); | |
| } | |
| /* ========================================================= | |
| VIEW: LOGIN | |
| ========================================================= */ | |
| .view { display: none; min-height: calc(100vh - 60px); } | |
| .view[data-active="true"] { display: block; } | |
| .login-wrap { | |
| min-height: calc(100vh - 60px); | |
| display: grid; | |
| grid-template-columns: 1fr minmax(400px, 480px); | |
| gap: 0; | |
| } | |
| .login-marketing { | |
| padding: 64px 56px; | |
| background: | |
| radial-gradient(ellipse at 20% 10%, color-mix(in oklab, var(--accent) 18%, transparent), transparent 60%), | |
| radial-gradient(ellipse at 80% 90%, color-mix(in oklab, var(--accent-2) 14%, transparent), transparent 55%), | |
| var(--base); | |
| position: relative; overflow: hidden; | |
| display: flex; flex-direction: column; justify-content: space-between; | |
| } | |
| .login-marketing::after { | |
| /* subtle grid */ | |
| content: ""; | |
| position: absolute; inset: 0; | |
| background-image: | |
| linear-gradient(color-mix(in oklab, var(--contrast-3) 12%, transparent) 1px, transparent 1px), | |
| linear-gradient(90deg, color-mix(in oklab, var(--contrast-3) 12%, transparent) 1px, transparent 1px); | |
| background-size: 56px 56px; | |
| mask-image: radial-gradient(ellipse at center, black 40%, transparent 80%); | |
| pointer-events: none; | |
| } | |
| .login-marketing > * { position: relative; z-index: 1; } | |
| .eyebrow { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; | |
| color: var(--accent); display: inline-flex; align-items: center; gap: 10px; | |
| } | |
| .eyebrow::before { | |
| content: ""; width: 24px; height: 1px; background: var(--accent); | |
| } | |
| .login-marketing h1 { | |
| font-size: clamp(36px, 4vw, 56px); line-height: 1.05; | |
| margin: 20px 0 18px; max-width: 12ch; | |
| } | |
| .login-marketing p.lede { | |
| font-size: 17px; color: var(--contrast-2); max-width: 46ch; margin: 0; | |
| } | |
| .login-stats { | |
| display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| gap: 24px; margin-top: 48px; max-width: 520px; | |
| } | |
| .login-stats .stat { | |
| border-top: 1px solid color-mix(in oklab, var(--contrast-3) 40%, transparent); | |
| padding-top: 12px; | |
| } | |
| .login-stats .stat .n { | |
| font-family: 'Fraunces', serif; font-size: 28px; color: var(--contrast); | |
| font-weight: 500; | |
| } | |
| .login-stats .stat .l { | |
| font-size: 12px; color: var(--contrast-2); font-family: 'JetBrains Mono', monospace; | |
| letter-spacing: 0.04em; | |
| } | |
| .login-card-wrap { | |
| background: var(--base-2); | |
| border-left: 1px solid color-mix(in oklab, var(--contrast-3) 35%, transparent); | |
| display: flex; align-items: center; justify-content: center; | |
| padding: 48px; | |
| } | |
| .login-card { | |
| width: 100%; max-width: 360px; | |
| display: grid; gap: 22px; | |
| } | |
| .login-card header h2 { | |
| font-size: 24px; margin-bottom: 6px; | |
| } | |
| .login-card header p { | |
| margin: 0; color: var(--contrast-2); font-size: 14px; | |
| } | |
| .login-form { display: grid; gap: 16px; } | |
| .login-footer { | |
| font-size: 12px; color: var(--contrast-2); | |
| padding-top: 8px; border-top: 1px solid color-mix(in oklab, var(--contrast-3) 30%, transparent); | |
| } | |
| .login-footer code { | |
| color: var(--accent); background: color-mix(in oklab, var(--accent) 10%, transparent); | |
| padding: 1px 5px; border-radius: 3px; font-size: 11px; | |
| } | |
| .error-simulator { | |
| display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; | |
| } | |
| .error-simulator button { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 10px; letter-spacing: 0.05em; text-transform: uppercase; | |
| padding: 5px 8px; border-radius: 4px; | |
| background: transparent; color: var(--contrast-2); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 40%, transparent); | |
| } | |
| .error-simulator button:hover { border-color: var(--accent); color: var(--accent); } | |
| /* ========================================================= | |
| VIEW: DASHBOARD | |
| ========================================================= */ | |
| .container { max-width: 1200px; margin: 0 auto; padding: 40px 28px 0; } | |
| .page-head { | |
| display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; flex-wrap: wrap; | |
| margin-bottom: 32px; | |
| } | |
| .page-head h1 { font-size: clamp(28px, 3vw, 40px); } | |
| .page-head .sub { color: var(--contrast-2); margin-top: 6px; font-size: 14px; } | |
| .page-head .term-chip { | |
| display: inline-flex; align-items: center; gap: 8px; | |
| font-family: 'JetBrains Mono', monospace; font-size: 12px; | |
| color: var(--contrast-2); | |
| padding: 6px 10px; border-radius: 999px; | |
| background: var(--base-2); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 40%, transparent); | |
| } | |
| .term-chip::before { | |
| content: ""; width: 8px; height: 8px; border-radius: 50%; | |
| background: var(--accent-2); | |
| box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent-2) 25%, transparent); | |
| } | |
| /* Continue watching strip */ | |
| .continue-strip { | |
| background: var(--base-2); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 30%, transparent); | |
| border-radius: var(--radius-lg); | |
| padding: 20px; | |
| margin-bottom: 32px; | |
| display: grid; grid-template-columns: 1fr auto; gap: 20px; align-items: center; | |
| } | |
| .continue-strip .thumb { | |
| width: 180px; aspect-ratio: 16/9; border-radius: var(--radius); | |
| background: | |
| repeating-linear-gradient(45deg, | |
| color-mix(in oklab, var(--accent) 15%, var(--base-3)) 0 8px, | |
| var(--base-3) 8px 16px); | |
| position: relative; overflow: hidden; | |
| grid-row: span 2; | |
| } | |
| .continue-strip .thumb::after { | |
| content: "▶"; position: absolute; inset: 0; | |
| display: grid; place-items: center; | |
| color: var(--contrast); font-size: 22px; | |
| background: color-mix(in oklab, var(--base) 40%, transparent); | |
| } | |
| .continue-body { | |
| display: grid; grid-template-columns: auto 1fr auto; gap: 20px; align-items: center; | |
| grid-column: 1 / -1; | |
| } | |
| .continue-meta { | |
| display: grid; gap: 6px; | |
| } | |
| .continue-meta .course-tag { | |
| font-family: 'JetBrains Mono', monospace; font-size: 11px; | |
| letter-spacing: 0.08em; text-transform: uppercase; | |
| color: var(--accent); | |
| } | |
| .continue-meta h3 { font-size: 20px; font-family: 'Inter', sans-serif; font-weight: 600; letter-spacing: -0.01em; } | |
| .continue-meta .descr { color: var(--contrast-2); font-size: 13px; } | |
| .progress { | |
| width: 100%; height: 3px; border-radius: 2px; | |
| background: color-mix(in oklab, var(--contrast-3) 35%, transparent); | |
| overflow: hidden; margin-top: 4px; | |
| } | |
| .progress > span { display: block; height: 100%; background: var(--accent); } | |
| /* Course grid */ | |
| .section-head { | |
| display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 16px; | |
| } | |
| .section-head h2 { font-size: 20px; font-family: 'Inter', sans-serif; font-weight: 600; letter-spacing: -0.01em; } | |
| .section-head .count { | |
| font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--contrast-2); | |
| } | |
| .course-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); | |
| gap: 20px; | |
| } | |
| .course-card { | |
| background: var(--base-2); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 30%, transparent); | |
| border-radius: var(--radius-lg); | |
| padding: 20px; | |
| display: grid; gap: 14px; | |
| transition: transform .15s var(--ease), border-color .15s var(--ease), background-color .15s var(--ease); | |
| cursor: pointer; | |
| position: relative; overflow: hidden; | |
| } | |
| .course-card:hover { | |
| transform: translateY(-2px); | |
| border-color: color-mix(in oklab, var(--accent) 50%, var(--contrast-3)); | |
| background: var(--base-3); | |
| } | |
| .course-card .code { | |
| font-family: 'JetBrains Mono', monospace; font-size: 12px; | |
| color: var(--accent); letter-spacing: 0.04em; | |
| } | |
| .course-card h3 { | |
| font-size: 18px; font-family: 'Inter', sans-serif; font-weight: 600; letter-spacing: -0.005em; | |
| line-height: 1.3; | |
| } | |
| .course-card .instructor { color: var(--contrast-2); font-size: 13px; } | |
| .course-card .meta { | |
| display: flex; justify-content: space-between; align-items: center; | |
| font-family: 'JetBrains Mono', monospace; font-size: 11px; | |
| color: var(--contrast-2); | |
| padding-top: 12px; | |
| border-top: 1px solid color-mix(in oklab, var(--contrast-3) 30%, transparent); | |
| } | |
| .course-card .meta strong { color: var(--contrast); font-weight: 500; } | |
| .course-card .latest { | |
| font-size: 12px; color: var(--contrast-2); | |
| padding: 8px 10px; background: var(--base-3); border-radius: var(--radius-sm); | |
| font-family: 'JetBrains Mono', monospace; | |
| display: flex; gap: 8px; align-items: center; | |
| } | |
| .course-card .latest::before { | |
| content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--accent-2); | |
| } | |
| /* Announcements */ | |
| .announcements { | |
| display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 40px; | |
| } | |
| .announcement { | |
| background: var(--base-2); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 30%, transparent); | |
| border-left: 2px solid var(--accent); | |
| padding: 16px 18px; border-radius: var(--radius); | |
| } | |
| .announcement .ts { | |
| font-family: 'JetBrains Mono', monospace; font-size: 11px; | |
| color: var(--contrast-2); letter-spacing: 0.04em; | |
| } | |
| .announcement h4 { font-size: 15px; font-family: 'Inter', sans-serif; font-weight: 600; margin: 6px 0 4px; } | |
| .announcement p { margin: 0; font-size: 13px; color: var(--contrast-2); } | |
| /* ========================================================= | |
| VIEW: DOCS | |
| ========================================================= */ | |
| .docs-layout { | |
| display: grid; | |
| grid-template-columns: 240px 1fr; | |
| gap: 48px; | |
| padding: 40px 28px 0; | |
| max-width: 1200px; margin: 0 auto; | |
| } | |
| .docs-nav { | |
| position: sticky; top: 84px; align-self: start; | |
| font-size: 14px; | |
| } | |
| .docs-nav h4 { | |
| font-family: 'JetBrains Mono', monospace; font-size: 11px; | |
| text-transform: uppercase; letter-spacing: 0.08em; | |
| color: var(--contrast-2); margin: 16px 0 8px; font-weight: 500; | |
| } | |
| .docs-nav h4:first-child { margin-top: 0; } | |
| .docs-nav a { | |
| display: block; padding: 6px 10px; border-radius: var(--radius-sm); | |
| color: var(--contrast-2); font-size: 13px; | |
| } | |
| .docs-nav a:hover { color: var(--contrast); background: color-mix(in oklab, var(--contrast) 6%, transparent); text-decoration: none; } | |
| .docs-nav a.active { color: var(--accent); background: color-mix(in oklab, var(--accent) 10%, transparent); } | |
| .docs-article { max-width: 72ch; } | |
| .docs-article .crumbs { | |
| font-family: 'JetBrains Mono', monospace; font-size: 12px; | |
| color: var(--contrast-2); margin-bottom: 18px; | |
| } | |
| .docs-article h1 { font-size: clamp(32px, 3vw, 44px); margin-bottom: 14px; } | |
| .docs-article .lede { font-size: 18px; color: var(--contrast-2); margin: 0 0 36px; } | |
| .docs-article h2 { | |
| font-size: 22px; margin-top: 42px; margin-bottom: 12px; | |
| padding-top: 20px; | |
| border-top: 1px solid color-mix(in oklab, var(--contrast-3) 30%, transparent); | |
| font-family: 'Inter', sans-serif; font-weight: 600; letter-spacing: -0.005em; | |
| } | |
| .docs-article p { margin: 0 0 14px; } | |
| .docs-article ol, .docs-article ul { padding-left: 22px; margin: 0 0 16px; } | |
| .docs-article li { margin-bottom: 6px; } | |
| .docs-article strong { color: var(--contrast); } | |
| .docs-article code { | |
| background: var(--base-2); color: var(--accent); | |
| padding: 1px 6px; border-radius: 4px; font-size: 0.9em; | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 25%, transparent); | |
| } | |
| .docs-article pre { | |
| background: var(--base-2); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 30%, transparent); | |
| border-radius: var(--radius); | |
| padding: 16px; | |
| overflow-x: auto; | |
| font-size: 13px; line-height: 1.6; | |
| } | |
| .docs-article pre code { | |
| background: transparent; border: 0; padding: 0; color: var(--contrast); | |
| } | |
| .callout { | |
| border: 1px solid color-mix(in oklab, var(--accent-2) 40%, transparent); | |
| background: color-mix(in oklab, var(--accent-2) 10%, transparent); | |
| padding: 14px 16px; border-radius: var(--radius); | |
| margin: 18px 0; font-size: 14px; | |
| } | |
| .callout strong { color: var(--accent-2); } | |
| /* ========================================================= | |
| THEME PICKER (floating, bottom-right) | |
| ========================================================= */ | |
| .picker-fab { | |
| position: fixed; right: 20px; bottom: 20px; z-index: 100; | |
| background: var(--base-2); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 40%, transparent); | |
| border-radius: var(--radius-lg); | |
| box-shadow: var(--shadow-2); | |
| overflow: hidden; | |
| max-width: 340px; | |
| font-size: 13px; | |
| transition: width .25s var(--ease), height .25s var(--ease); | |
| } | |
| .picker-head { | |
| display: flex; align-items: center; gap: 10px; | |
| padding: 12px 14px; | |
| cursor: pointer; | |
| user-select: none; | |
| border-bottom: 1px solid color-mix(in oklab, var(--contrast-3) 30%, transparent); | |
| } | |
| .picker-head .dot { | |
| width: 10px; height: 10px; border-radius: 50%; | |
| background: var(--accent); | |
| box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 25%, transparent); | |
| } | |
| .picker-head .label { color: var(--contrast); font-weight: 600; font-size: 13px; } | |
| .picker-head .mode { | |
| margin-left: auto; | |
| font-family: 'JetBrains Mono', monospace; font-size: 10px; | |
| color: var(--contrast-2); letter-spacing: 0.05em; text-transform: uppercase; | |
| } | |
| .picker-head .chev { | |
| margin-left: 8px; color: var(--contrast-2); | |
| transition: transform .2s var(--ease); | |
| } | |
| .picker[data-open="true"] .chev { transform: rotate(180deg); } | |
| .picker-body { display: none; padding: 12px 14px; } | |
| .picker[data-open="true"] .picker-body { display: block; } | |
| .picker-body .mode-row { | |
| display: flex; gap: 6px; | |
| background: var(--base-3); padding: 3px; border-radius: var(--radius); | |
| margin-bottom: 12px; | |
| } | |
| .picker-body .mode-row button { | |
| flex: 1; padding: 6px 8px; | |
| background: transparent; border: 0; | |
| font-family: 'JetBrains Mono', monospace; font-size: 11px; | |
| color: var(--contrast-2); letter-spacing: 0.04em; text-transform: uppercase; | |
| border-radius: calc(var(--radius) - 3px); | |
| transition: background-color .15s var(--ease), color .15s var(--ease); | |
| } | |
| .picker-body .mode-row button[aria-selected="true"] { | |
| background: var(--base-2); color: var(--contrast); | |
| } | |
| .picker-search { | |
| width: 100%; padding: 8px 10px; | |
| background: var(--base-3); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 35%, transparent); | |
| color: var(--contrast); | |
| border-radius: var(--radius-sm); font-size: 12px; | |
| margin-bottom: 10px; | |
| } | |
| .theme-list { max-height: 280px; overflow-y: auto; display: grid; gap: 4px; margin: 0 -4px 12px; padding: 0 4px; } | |
| .theme-list::-webkit-scrollbar { width: 6px; } | |
| .theme-list::-webkit-scrollbar-thumb { background: var(--base-3); border-radius: 3px; } | |
| .theme-item { | |
| display: grid; grid-template-columns: auto 1fr auto; gap: 10px; align-items: center; | |
| padding: 8px 10px; border-radius: var(--radius-sm); | |
| background: transparent; border: 1px solid transparent; | |
| cursor: pointer; text-align: left; | |
| color: var(--contrast); font-size: 13px; | |
| transition: background-color .12s var(--ease), border-color .12s var(--ease); | |
| } | |
| .theme-item:hover { background: var(--base-3); } | |
| .theme-item[aria-current="true"] { border-color: var(--accent); background: color-mix(in oklab, var(--accent) 8%, transparent); } | |
| .swatch-row { display: flex; gap: 2px; } | |
| .swatch-row span { width: 10px; height: 14px; border-radius: 2px; } | |
| .theme-item .meta { | |
| font-family: 'JetBrains Mono', monospace; font-size: 10px; | |
| color: var(--contrast-2); letter-spacing: 0.04em; | |
| } | |
| .theme-item .kind { | |
| font-family: 'JetBrains Mono', monospace; font-size: 9px; | |
| color: var(--contrast-2); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 40%, transparent); | |
| padding: 1px 5px; border-radius: 3px; | |
| text-transform: uppercase; letter-spacing: 0.06em; | |
| } | |
| .theme-item[data-kind="stock"] .kind { color: var(--accent-2); border-color: color-mix(in oklab, var(--accent-2) 40%, transparent); } | |
| .picker-actions { | |
| display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-bottom: 10px; | |
| } | |
| .picker-actions .chip { | |
| font-family: 'JetBrains Mono', monospace; font-size: 10px; | |
| letter-spacing: 0.06em; text-transform: uppercase; | |
| padding: 8px 10px; border-radius: var(--radius-sm); | |
| background: transparent; color: var(--contrast-2); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 40%, transparent); | |
| cursor: pointer; | |
| display: flex; align-items: center; justify-content: center; gap: 6px; | |
| } | |
| .picker-actions .chip[aria-pressed="true"] { | |
| color: var(--accent); border-color: var(--accent); | |
| background: color-mix(in oklab, var(--accent) 10%, transparent); | |
| } | |
| .picker-footer { | |
| display: flex; justify-content: space-between; align-items: center; | |
| padding-top: 10px; | |
| border-top: 1px solid color-mix(in oklab, var(--contrast-3) 25%, transparent); | |
| font-family: 'JetBrains Mono', monospace; font-size: 10px; | |
| color: var(--contrast-2); letter-spacing: 0.05em; | |
| } | |
| .picker-footer a { color: var(--contrast-2); text-decoration: underline; } | |
| .picker-footer a:hover { color: var(--accent); } | |
| /* ========================================================= | |
| DIAGNOSTIC DRAWER (dev-facing) | |
| ========================================================= */ | |
| .diag-toggle { | |
| position: fixed; right: 20px; top: 20px; z-index: 90; | |
| background: var(--base-2); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 40%, transparent); | |
| color: var(--contrast-2); | |
| font-family: 'JetBrains Mono', monospace; font-size: 11px; | |
| padding: 6px 10px; border-radius: var(--radius-sm); | |
| letter-spacing: 0.06em; text-transform: uppercase; | |
| } | |
| .diag-toggle:hover { color: var(--accent); border-color: var(--accent); } | |
| .diag-drawer { | |
| position: fixed; top: 0; right: 0; height: 100vh; width: 420px; | |
| background: var(--base-2); | |
| border-left: 1px solid color-mix(in oklab, var(--contrast-3) 40%, transparent); | |
| z-index: 95; | |
| transform: translateX(100%); | |
| transition: transform .25s var(--ease); | |
| overflow-y: auto; | |
| font-family: 'JetBrains Mono', monospace; font-size: 12px; | |
| padding: 20px; | |
| } | |
| .diag-drawer[data-open="true"] { transform: translateX(0); } | |
| .diag-drawer h3 { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; | |
| color: var(--contrast-2); margin: 18px 0 8px; | |
| } | |
| .diag-drawer h3:first-child { margin-top: 0; } | |
| .diag-drawer .close { | |
| position: absolute; right: 12px; top: 12px; | |
| background: transparent; border: 0; color: var(--contrast-2); | |
| font-family: inherit; font-size: 16px; | |
| } | |
| .diag-drawer .close:hover { color: var(--accent); } | |
| .kv { display: grid; grid-template-columns: 110px 1fr; gap: 4px 12px; font-size: 11px; } | |
| .kv dt { color: var(--contrast-2); } | |
| .kv dd { margin: 0; color: var(--contrast); word-break: break-all; } | |
| .vars-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; font-size: 11px; } | |
| .var-pill { | |
| display: grid; grid-template-columns: 14px 1fr auto; gap: 6px; align-items: center; | |
| padding: 5px 8px; border-radius: 4px; | |
| background: var(--base-3); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 30%, transparent); | |
| } | |
| .var-pill .chip { | |
| width: 14px; height: 14px; border-radius: 3px; | |
| border: 1px solid color-mix(in oklab, var(--contrast) 20%, transparent); | |
| } | |
| .var-pill .name { color: var(--contrast-2); font-size: 10px; } | |
| .var-pill .val { color: var(--contrast); font-size: 10px; } | |
| .code-tabs { display: flex; gap: 2px; margin-bottom: 6px; } | |
| .code-tabs button { | |
| flex: 1; padding: 5px 8px; font-size: 10px; | |
| background: var(--base-3); color: var(--contrast-2); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 30%, transparent); | |
| border-radius: var(--radius-sm); cursor: pointer; | |
| font-family: inherit; letter-spacing: 0.04em; text-transform: uppercase; | |
| } | |
| .code-tabs button[aria-selected="true"] { color: var(--accent); border-color: var(--accent); } | |
| .code-block { | |
| position: relative; | |
| background: var(--base); color: var(--contrast); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 30%, transparent); | |
| border-radius: var(--radius-sm); | |
| padding: 10px 12px; font-size: 10px; line-height: 1.55; | |
| white-space: pre; overflow-x: auto; | |
| max-height: 220px; | |
| } | |
| .copy-btn { | |
| position: absolute; top: 6px; right: 6px; | |
| font-family: inherit; font-size: 9px; | |
| letter-spacing: 0.06em; text-transform: uppercase; | |
| background: var(--base-2); color: var(--contrast-2); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 40%, transparent); | |
| border-radius: 3px; padding: 3px 6px; cursor: pointer; | |
| } | |
| .copy-btn:hover { color: var(--accent); border-color: var(--accent); } | |
| .log { | |
| font-size: 10px; line-height: 1.55; | |
| max-height: 120px; overflow-y: auto; | |
| background: var(--base); padding: 8px 10px; border-radius: var(--radius-sm); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 25%, transparent); | |
| } | |
| .log .l-ok { color: var(--accent-2); } | |
| .log .l-err { color: var(--accent); } | |
| .log .l-info { color: var(--contrast-2); } | |
| /* View switcher (demo tool) */ | |
| .view-switch { | |
| position: fixed; left: 20px; bottom: 20px; z-index: 100; | |
| display: flex; gap: 3px; | |
| background: var(--base-2); | |
| border: 1px solid color-mix(in oklab, var(--contrast-3) 40%, transparent); | |
| border-radius: var(--radius); padding: 3px; | |
| box-shadow: var(--shadow-2); | |
| } | |
| .view-switch button { | |
| padding: 8px 12px; background: transparent; border: 0; | |
| font-family: 'JetBrains Mono', monospace; font-size: 11px; | |
| color: var(--contrast-2); letter-spacing: 0.05em; text-transform: uppercase; | |
| border-radius: calc(var(--radius) - 3px); cursor: pointer; | |
| } | |
| .view-switch button[aria-current="true"] { | |
| background: var(--base-3); color: var(--contrast); | |
| } | |
| @media (max-width: 860px) { | |
| .login-wrap { grid-template-columns: 1fr; } | |
| .login-marketing { padding: 40px 28px; } | |
| .login-card-wrap { padding: 32px 28px; border-left: 0; border-top: 1px solid color-mix(in oklab, var(--contrast-3) 30%, transparent); } | |
| .docs-layout { grid-template-columns: 1fr; } | |
| .docs-nav { position: static; } | |
| .announcements { grid-template-columns: 1fr; } | |
| .diag-drawer { width: 100vw; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- ========================================================= | |
| VIEW SWITCHER (demo scaffolding, not part of the real portal) | |
| ========================================================= --> | |
| <div class="view-switch" role="tablist" aria-label="Demo views"> | |
| <button role="tab" aria-current="true" data-view="login">Login</button> | |
| <button role="tab" data-view="dashboard">Dashboard</button> | |
| <button role="tab" data-view="docs">Docs</button> | |
| </div> | |
| <button class="diag-toggle" id="diagToggle" title="Open theme diagnostics">◱ Theme Inspector</button> | |
| <!-- ========================================================= | |
| VIEW: LOGIN | |
| ========================================================= --> | |
| <section class="view" data-view="login" data-active="true"> | |
| <div class="login-wrap"> | |
| <div class="login-marketing"> | |
| <div> | |
| <span class="eyebrow">Engineering Outreach</span> | |
| <h1>Remote lectures for working engineers.</h1> | |
| <p class="lede">Pre-recorded coursework from University of Idaho's College of Engineering. Watch on your schedule; your cohort, your pace.</p> | |
| </div> | |
| <div class="login-stats"> | |
| <div class="stat"><div class="n">40+</div><div class="l">Courses / term</div></div> | |
| <div class="stat"><div class="n">12</div><div class="l">Degree programs</div></div> | |
| <div class="stat"><div class="n">24/7</div><div class="l">Lecture access</div></div> | |
| </div> | |
| </div> | |
| <div class="login-card-wrap"> | |
| <div class="login-card"> | |
| <header> | |
| <div class="brand" style="margin-bottom: 28px;"> | |
| <div class="brand-mark">Ui</div> | |
| <div> | |
| <div class="brand-name">EO Portal</div> | |
| <div class="brand-sub">University of Idaho · Engineering Outreach</div> | |
| </div> | |
| </div> | |
| <h2>Sign in to your portal</h2> | |
| <p>Enter the credentials issued to you by your program advisor.</p> | |
| </header> | |
| <div id="loginAlert" class="alert" style="display: none;"> | |
| <div id="loginAlertText">Incorrect username or password. Please try again.</div> | |
| </div> | |
| <form class="login-form" id="loginForm" autocomplete="off"> | |
| <div class="field"> | |
| <label for="username">Username</label> | |
| <input class="input" type="text" id="username" placeholder="vandal.engineer" autocomplete="username"> | |
| </div> | |
| <div class="field"> | |
| <label for="password">Password</label> | |
| <input class="input" type="password" id="password" placeholder="••••••••" autocomplete="current-password"> | |
| </div> | |
| <div class="checkbox-row"> | |
| <input type="checkbox" id="remember" checked> | |
| <label for="remember" style="color: var(--contrast-2); font-size: 13px;">Keep me signed in on this device</label> | |
| </div> | |
| <button type="submit" class="btn btn-primary btn-block">Sign in</button> | |
| </form> | |
| <div class="login-footer"> | |
| Lost your credentials? Contact your program advisor. | |
| New students are provisioned at registration — there is no self-signup. | |
| <div class="error-simulator"> | |
| <button type="button" data-err="invalid">Simulate invalid</button> | |
| <button type="button" data-err="inactive">Simulate inactive</button> | |
| <button type="button" data-err="clear">Clear</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- ========================================================= | |
| VIEW: DASHBOARD | |
| ========================================================= --> | |
| <section class="view" data-view="dashboard"> | |
| <header class="site-header"> | |
| <div class="brand"> | |
| <div class="brand-mark">Ui</div> | |
| <div class="brand-name">EO Portal</div> | |
| </div> | |
| <nav class="nav" aria-label="Primary"> | |
| <a href="#" aria-current="page">Courses</a> | |
| <a href="#">Library</a> | |
| <a href="#">Schedule</a> | |
| <a href="#">Documents</a> | |
| </nav> | |
| <div class="header-right"> | |
| <div class="user-chip"> | |
| <div class="avatar">MS</div> | |
| <span>Morgan Sawyer</span> | |
| </div> | |
| </div> | |
| </header> | |
| <div class="container"> | |
| <div class="page-head"> | |
| <div> | |
| <h1>Welcome back, Morgan.</h1> | |
| <div class="sub">You have 3 new lectures this week across your enrolled courses.</div> | |
| </div> | |
| <span class="term-chip">Spring Term 2026 · Week 12</span> | |
| </div> | |
| <div class="continue-strip"> | |
| <div class="continue-body"> | |
| <div class="thumb"></div> | |
| <div class="continue-meta"> | |
| <div class="course-tag">ME 340 · Heat Transfer</div> | |
| <h3>Convection over flat plates — boundary layer analysis</h3> | |
| <div class="descr">Prof. Helena Park · Lecture 18 of 32 · 44 min remaining</div> | |
| <div class="progress" aria-label="Progress"><span style="width: 38%;"></span></div> | |
| </div> | |
| <button class="btn btn-primary">Resume</button> | |
| </div> | |
| </div> | |
| <div class="section-head"> | |
| <h2>Your courses</h2> | |
| <span class="count">6 enrolled · spring term</span> | |
| </div> | |
| <div class="course-grid" id="courseGrid"></div> | |
| <div class="section-head" style="margin-top: 48px;"> | |
| <h2>Announcements</h2> | |
| </div> | |
| <div class="announcements"> | |
| <div class="announcement"> | |
| <div class="ts">APR 18 · 10:24</div> | |
| <h4>ME 340 midterm recordings posted</h4> | |
| <p>Exam review sessions are available under your course's Library tab through April 30.</p> | |
| </div> | |
| <div class="announcement"> | |
| <div class="ts">APR 16 · 14:02</div> | |
| <h4>Maintenance window Sunday 2–4am PT</h4> | |
| <p>The portal will be briefly unavailable while lecture storage is migrated.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <footer class="site-footer"> | |
| <div>© University of Idaho · Engineering Outreach · eo.uidaho.edu</div> | |
| <div class="mono">themed via omarchy — <span id="activeThemeFooter">futurism</span></div> | |
| </footer> | |
| </section> | |
| <!-- ========================================================= | |
| VIEW: DOCS | |
| ========================================================= --> | |
| <section class="view" data-view="docs"> | |
| <header class="site-header"> | |
| <div class="brand"> | |
| <div class="brand-mark">Ui</div> | |
| <div class="brand-name">EO Portal</div> | |
| </div> | |
| <nav class="nav" aria-label="Primary"> | |
| <a href="#">Courses</a> | |
| <a href="#">Library</a> | |
| <a href="#">Schedule</a> | |
| <a href="#" aria-current="page">Documents</a> | |
| </nav> | |
| <div class="header-right"> | |
| <div class="user-chip"><div class="avatar">MS</div><span>Morgan Sawyer</span></div> | |
| </div> | |
| </header> | |
| <div class="docs-layout"> | |
| <aside class="docs-nav"> | |
| <h4>Getting started</h4> | |
| <a href="#" class="active">Accessing your portal</a> | |
| <a href="#">System requirements</a> | |
| <a href="#">Downloading lectures</a> | |
| <h4>Coursework</h4> | |
| <a href="#">Assignment submission</a> | |
| <a href="#">Proctored exams</a> | |
| <a href="#">Grades & transcripts</a> | |
| <h4>Support</h4> | |
| <a href="#">Technical issues</a> | |
| <a href="#">Contact your advisor</a> | |
| </aside> | |
| <article class="docs-article"> | |
| <div class="crumbs">Documents / Getting started / Accessing your portal</div> | |
| <h1>Accessing your portal</h1> | |
| <p class="lede">Everything you need to know about signing into the Engineering Outreach portal and keeping your credentials current.</p> | |
| <p>The portal uses a credential set issued to you during program registration. Your username is typically the same as your University of Idaho NetID, though your portal password is managed separately from your university-wide account.</p> | |
| <h2>Signing in</h2> | |
| <ol> | |
| <li>Navigate to <code>eo.uidaho.edu/portal</code>.</li> | |
| <li>Enter the username and password provided by your program advisor.</li> | |
| <li>Optional: check <strong>Keep me signed in on this device</strong> for faster access from a personal machine.</li> | |
| </ol> | |
| <div class="callout"> | |
| <strong>Note:</strong> There is no self-service registration. If you are a new student and have not yet received credentials, reach out to your program advisor — we provision accounts manually to confirm enrollment. | |
| </div> | |
| <h2>Troubleshooting failed sign-ins</h2> | |
| <p>If your credentials are rejected, the portal will display a generic error. This is intentional — we don't indicate whether a username exists. Common causes:</p> | |
| <ul> | |
| <li>Password was recently reset and your browser cached the old one.</li> | |
| <li>Your enrollment status lapsed between terms. Re-enrollment re-activates access.</li> | |
| <li>Caps Lock is on. (Surprisingly common.)</li> | |
| </ul> | |
| <h2>Browser theming (Omarchy users)</h2> | |
| <p>The portal reads your Chromium-exposed OS accent color and will automatically match your active Omarchy theme. To force a specific theme, use the picker in the bottom-right:</p> | |
| <pre><code># Your active theme is written by Omarchy at: | |
| ~/.config/omarchy/current/theme.name | |
| # The portal fetches the matching colors.toml at: | |
| github.com/<author>/omarchy-<theme>-theme/blob/main/colors.toml</code></pre> | |
| <h2>Session length</h2> | |
| <p>Sessions last 30 days with <strong>Keep me signed in</strong> enabled, 2 hours without. You can end all sessions from Account Settings.</p> | |
| </article> | |
| </div> | |
| </section> | |
| <!-- ========================================================= | |
| THEME PICKER (floating) | |
| ========================================================= --> | |
| <aside class="picker-fab picker" id="picker" aria-label="Theme picker"> | |
| <div class="picker-head" id="pickerHead"> | |
| <div class="dot" id="pickerDot"></div> | |
| <div> | |
| <div class="label" id="pickerLabel">Futurism</div> | |
| </div> | |
| <div class="mode" id="pickerMode">MANUAL</div> | |
| <div class="chev">▾</div> | |
| </div> | |
| <div class="picker-body"> | |
| <div class="mode-row" role="tablist"> | |
| <button role="tab" data-mode="auto" aria-selected="false">Auto‑detect</button> | |
| <button role="tab" data-mode="manual" aria-selected="true">Choose theme</button> | |
| </div> | |
| <input class="picker-search" placeholder="Search themes…" id="themeSearch"> | |
| <div class="theme-list" id="themeList"></div> | |
| <div class="picker-actions"> | |
| <button class="chip" id="grayscaleBtn" aria-pressed="false">▦ Grayscale</button> | |
| <button class="chip" id="fetchLiveBtn">↻ Fetch from web</button> | |
| </div> | |
| <div class="picker-footer"> | |
| <span id="cacheNote">CACHED · 2 min ago</span> | |
| <a href="#" id="openDiag">Inspect →</a> | |
| </div> | |
| </div> | |
| </aside> | |
| <!-- ========================================================= | |
| DIAGNOSTIC DRAWER | |
| ========================================================= --> | |
| <aside class="diag-drawer" id="diagDrawer"> | |
| <button class="close" id="diagClose" aria-label="Close">×</button> | |
| <h3>Detection</h3> | |
| <dl class="kv"> | |
| <dt>Mode</dt><dd id="d-mode">manual</dd> | |
| <dt>AccentColor</dt><dd id="d-accent">—</dd> | |
| <dt>Canvas</dt><dd id="d-canvas">—</dd> | |
| <dt>prefers-color-scheme</dt><dd id="d-pcs">—</dd> | |
| <dt>Detected theme</dt><dd id="d-detected">—</dd> | |
| </dl> | |
| <h3>Active palette</h3> | |
| <dl class="kv"> | |
| <dt>Theme</dt><dd id="d-theme">futurism</dd> | |
| <dt>Source</dt><dd id="d-source">bundled default</dd> | |
| <dt>Fetched at</dt><dd id="d-fetched">—</dd> | |
| </dl> | |
| <div class="vars-grid" id="varsGrid" style="margin-top: 10px;"></div> | |
| <h3>Export</h3> | |
| <div class="code-tabs" role="tablist"> | |
| <button role="tab" data-tab="css" aria-selected="true">CSS</button> | |
| <button role="tab" data-tab="php" aria-selected="false">GP filter</button> | |
| <button role="tab" data-tab="json" aria-selected="false">JSON</button> | |
| </div> | |
| <div class="code-block"> | |
| <button class="copy-btn" id="copyBtn">Copy</button> | |
| <code id="exportCode"></code> | |
| </div> | |
| <h3>Fetch log</h3> | |
| <div class="log" id="fetchLog"></div> | |
| </aside> | |
| <script> | |
| /* ========================================================= | |
| THEME REGISTRY | |
| Each theme is a sparse representation derived from an | |
| Omarchy colors.toml. In production, these would be fetched | |
| on demand; here we seed a handful for the demo. | |
| ========================================================= */ | |
| const SEED_THEMES = [ | |
| { id: 'futurism', name: 'Futurism', kind: 'stock', author: 'bjarneo', | |
| toml: { background: '#0A1428', foreground: '#F0F8FF', accent: '#FF40A3', | |
| c2: '#00BFFF', c3: '#5076B2' } }, | |
| { id: 'tokyo-night', name: 'Tokyo Night', kind: 'stock', author: 'basecamp', | |
| toml: { background: '#1a1b26', foreground: '#c0caf5', accent: '#7aa2f7', | |
| c2: '#bb9af7', c3: '#565f89' } }, | |
| { id: 'catppuccin', name: 'Catppuccin', kind: 'stock', author: 'basecamp', | |
| toml: { background: '#1e1e2e', foreground: '#cdd6f4', accent: '#cba6f7', | |
| c2: '#89b4fa', c3: '#6c7086' } }, | |
| { id: 'catppuccin-latte', name: 'Catppuccin Latte', kind: 'stock', author: 'basecamp', | |
| toml: { background: '#eff1f5', foreground: '#4c4f69', accent: '#8839ef', | |
| c2: '#1e66f5', c3: '#9ca0b0' } }, | |
| { id: 'gruvbox', name: 'Gruvbox', kind: 'stock', author: 'basecamp', | |
| toml: { background: '#282828', foreground: '#ebdbb2', accent: '#fe8019', | |
| c2: '#8ec07c', c3: '#928374' } }, | |
| { id: 'nord', name: 'Nord', kind: 'stock', author: 'basecamp', | |
| toml: { background: '#2e3440', foreground: '#eceff4', accent: '#88c0d0', | |
| c2: '#a3be8c', c3: '#4c566a' } }, | |
| { id: 'rose-pine', name: 'Rose Pine', kind: 'stock', author: 'basecamp', | |
| toml: { background: '#191724', foreground: '#e0def4', accent: '#ebbcba', | |
| c2: '#9ccfd8', c3: '#6e6a86' } }, | |
| { id: 'everforest', name: 'Everforest', kind: 'stock', author: 'basecamp', | |
| toml: { background: '#2d353b', foreground: '#d3c6aa', accent: '#a7c080', | |
| c2: '#7fbbb3', c3: '#5c6a72' } }, | |
| { id: 'kanagawa', name: 'Kanagawa', kind: 'stock', author: 'basecamp', | |
| toml: { background: '#1f1f28', foreground: '#dcd7ba', accent: '#7e9cd8', | |
| c2: '#e82424', c3: '#54546d' } }, | |
| { id: 'matte-black', name: 'Matte Black', kind: 'stock', author: 'basecamp', | |
| toml: { background: '#121212', foreground: '#e0e0e0', accent: '#ff6b35', | |
| c2: '#4ecdc4', c3: '#3a3a3a' } }, | |
| { id: 'osaka-jade', name: 'Osaka Jade', kind: 'stock', author: 'basecamp', | |
| toml: { background: '#0d1f1a', foreground: '#c5e4d3', accent: '#4ac29a', | |
| c2: '#b4d4be', c3: '#2d4a3e' } }, | |
| { id: 'ristretto', name: 'Ristretto', kind: 'stock', author: 'basecamp', | |
| toml: { background: '#2c2525', foreground: '#fff1f3', accent: '#f38ba3', | |
| c2: '#e5a3a3', c3: '#72696a' } }, | |
| { id: 'hackerman', name: 'Hackerman', kind: 'stock', author: 'basecamp', | |
| toml: { background: '#0d0208', foreground: '#00ff41', accent: '#008f11', | |
| c2: '#003b00', c3: '#1a1a1a' } }, | |
| { id: 'lumon', name: 'Lumon', kind: 'stock', author: 'basecamp', | |
| toml: { background: '#0a1f2e', foreground: '#7dd3fc', accent: '#38bdf8', | |
| c2: '#e0f2fe', c3: '#1e3a5f' } }, | |
| { id: 'vantablack', name: 'Vantablack', kind: 'stock', author: 'basecamp', | |
| toml: { background: '#000000', foreground: '#ffffff', accent: '#ff0080', | |
| c2: '#00ffff', c3: '#222222' } }, | |
| { id: 'rose-pine-dawn', name: 'Rose Pine Dawn', kind: 'community', author: 'rose-pine', | |
| toml: { background: '#faf4ed', foreground: '#575279', accent: '#d7827e', | |
| c2: '#56949f', c3: '#cecacd' } }, | |
| { id: 'cobalt2', name: 'Cobalt2', kind: 'community', author: 'hoblin', | |
| toml: { background: '#193549', foreground: '#ffffff', accent: '#ffc600', | |
| c2: '#ff9d00', c3: '#0d3a58' } }, | |
| { id: 'synthwave84', name: 'Synthwave 84', kind: 'community', author: 'robb0wen', | |
| toml: { background: '#241b2f', foreground: '#ffffff', accent: '#ff7edb', | |
| c2: '#36f9f6', c3: '#495495' } }, | |
| { id: 'solarized', name: 'Solarized', kind: 'community', author: 'ethanschoonover', | |
| toml: { background: '#002b36', foreground: '#839496', accent: '#268bd2', | |
| c2: '#2aa198', c3: '#586e75' } }, | |
| ]; | |
| /* ========================================================= | |
| COLOR MATH — derive the 7 GP variables from a minimal | |
| colors.toml projection. Rules: | |
| Dark themes (bg L < 0.5): | |
| --base = bg | |
| --base-2 = bg lifted +4% L | |
| --base-3 = bg lifted +8% L | |
| --contrast = fg | |
| --contrast-2 = fg dimmed toward bg, 35% | |
| --contrast-3 = fg dimmed toward bg, 65% OR toml.c3 if sensible | |
| --accent = toml.accent | |
| Light themes are symmetric (base darkens slightly). | |
| This logic runs deterministically on ANY new theme we | |
| haven't seen — so community themes "just work" as long as | |
| their colors.toml has background/foreground/accent. | |
| ========================================================= */ | |
| function hexToRgb(h) { | |
| const n = h.replace('#',''); | |
| return [parseInt(n.slice(0,2),16), parseInt(n.slice(2,4),16), parseInt(n.slice(4,6),16)]; | |
| } | |
| function rgbToHex([r,g,b]) { | |
| return '#' + [r,g,b].map(v => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2,'0')).join(''); | |
| } | |
| function luminance([r,g,b]) { | |
| const f = c => { c /= 255; return c <= 0.03928 ? c/12.92 : Math.pow((c+0.055)/1.055, 2.4); }; | |
| return 0.2126*f(r) + 0.7152*f(g) + 0.0722*f(b); | |
| } | |
| function mix(a, b, t) { | |
| const A = hexToRgb(a), B = hexToRgb(b); | |
| return rgbToHex([A[0]+(B[0]-A[0])*t, A[1]+(B[1]-A[1])*t, A[2]+(B[2]-A[2])*t]); | |
| } | |
| function derivePalette(toml) { | |
| const bg = toml.background, fg = toml.foreground; | |
| const isDark = luminance(hexToRgb(bg)) < 0.4; | |
| const lift = isDark ? '#ffffff' : '#000000'; | |
| const dim = isDark ? bg : fg; | |
| return { | |
| '--base': bg, | |
| '--base-2': mix(bg, lift, 0.05), | |
| '--base-3': mix(bg, lift, 0.11), | |
| '--contrast': fg, | |
| '--contrast-2': mix(fg, dim, 0.30), | |
| '--contrast-3': toml.c3 || mix(fg, dim, 0.60), | |
| '--accent': toml.accent, | |
| '--accent-2': toml.c2 || toml.accent, | |
| }; | |
| } | |
| /* ========================================================= | |
| THEME APPLICATION | |
| ========================================================= */ | |
| let activeTheme = SEED_THEMES[0]; | |
| let activePalette = derivePalette(activeTheme.toml); | |
| let currentMode = 'manual'; | |
| let cacheInfo = { fetchedAt: Date.now(), source: 'bundled default' }; | |
| function applyTheme(theme, {source='bundled default', fetched=Date.now()} = {}) { | |
| activeTheme = theme; | |
| activePalette = derivePalette(theme.toml); | |
| cacheInfo = { fetchedAt: fetched, source }; | |
| const root = document.documentElement; | |
| for (const [k, v] of Object.entries(activePalette)) root.style.setProperty(k, v); | |
| renderPicker(); | |
| renderDiagnostics(); | |
| document.getElementById('activeThemeFooter').textContent = theme.id; | |
| log('ok', `Applied theme "${theme.id}" (${Object.keys(activePalette).length} vars)`); | |
| } | |
| /* ========================================================= | |
| DETECTION — read OS-exposed system colors | |
| ========================================================= */ | |
| function readSystemColor(name) { | |
| const el = document.createElement('div'); | |
| el.style.color = name; | |
| document.body.appendChild(el); | |
| const rgb = getComputedStyle(el).color; | |
| el.remove(); | |
| const m = rgb.match(/\d+/g); | |
| if (!m) return null; | |
| return rgbToHex([+m[0], +m[1], +m[2]]); | |
| } | |
| function detectTheme() { | |
| const accent = readSystemColor('AccentColor'); | |
| const canvas = readSystemColor('Canvas'); | |
| const pcs = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; | |
| let best = null, bestScore = 1e9; | |
| for (const t of SEED_THEMES) { | |
| const ta = hexToRgb(t.toml.accent), tb = hexToRgb(t.toml.background); | |
| const ua = accent ? hexToRgb(accent) : null; | |
| const ub = canvas ? hexToRgb(canvas) : null; | |
| let score = 0; | |
| if (ua) score += Math.hypot(ta[0]-ua[0], ta[1]-ua[1], ta[2]-ua[2]); | |
| if (ub) score += Math.hypot(tb[0]-ub[0], tb[1]-ub[1], tb[2]-ub[2]) * 0.5; | |
| if (!ua && !ub) score = 1e9; | |
| if (score < bestScore) { bestScore = score; best = t; } | |
| } | |
| return { accent, canvas, pcs, best, score: bestScore }; | |
| } | |
| /* ========================================================= | |
| FAKE FETCH — simulate pulling colors.toml from GitHub. | |
| In a real WP plugin this would hit /wp-json/omarchy/v1/theme/<id> | |
| which caches + proxies to github.com raw content. | |
| ========================================================= */ | |
| function fakeFetch(themeId) { | |
| return new Promise((resolve, reject) => { | |
| log('info', `GET github.com/.../${themeId}/colors.toml`); | |
| setTimeout(() => { | |
| const t = SEED_THEMES.find(x => x.id === themeId); | |
| if (!t) { log('err', `404: theme "${themeId}" not in registry`); reject('404'); return; } | |
| log('ok', `200 · parsed colors.toml (${Object.keys(t.toml).length} keys)`); | |
| resolve(t); | |
| }, 380 + Math.random() * 420); | |
| }); | |
| } | |
| /* ========================================================= | |
| PICKER UI | |
| ========================================================= */ | |
| const picker = document.getElementById('picker'); | |
| const pickerHead = document.getElementById('pickerHead'); | |
| const themeListEl = document.getElementById('themeList'); | |
| const themeSearchEl = document.getElementById('themeSearch'); | |
| pickerHead.addEventListener('click', (e) => { | |
| if (e.target.closest('.mode-row')) return; | |
| picker.dataset.open = picker.dataset.open === 'true' ? 'false' : 'true'; | |
| }); | |
| function renderPicker() { | |
| const q = (themeSearchEl.value || '').toLowerCase().trim(); | |
| const filtered = SEED_THEMES.filter(t => | |
| !q || t.name.toLowerCase().includes(q) || t.id.includes(q) || t.author.toLowerCase().includes(q)); | |
| themeListEl.innerHTML = filtered.map(t => { | |
| const p = derivePalette(t.toml); | |
| const active = t.id === activeTheme.id ? 'true' : 'false'; | |
| return `<button class="theme-item" data-theme="${t.id}" data-kind="${t.kind}" aria-current="${active}"> | |
| <span class="swatch-row"> | |
| <span style="background:${p['--base']}"></span> | |
| <span style="background:${p['--base-2']}"></span> | |
| <span style="background:${p['--contrast']}"></span> | |
| <span style="background:${p['--accent']}"></span> | |
| <span style="background:${p['--accent-2']}"></span> | |
| </span> | |
| <span> | |
| <div>${t.name}</div> | |
| <div class="meta">${t.author}</div> | |
| </span> | |
| <span class="kind">${t.kind}</span> | |
| </button>`; | |
| }).join(''); | |
| themeListEl.querySelectorAll('.theme-item').forEach(btn => { | |
| btn.addEventListener('click', async () => { | |
| setMode('manual'); | |
| try { | |
| const t = await fakeFetch(btn.dataset.theme); | |
| applyTheme(t, { source: `github.com/${t.author}/omarchy-${t.id}-theme`, fetched: Date.now() }); | |
| } catch {} | |
| }); | |
| }); | |
| document.getElementById('pickerLabel').textContent = activeTheme.name; | |
| document.getElementById('pickerDot').style.background = activePalette['--accent']; | |
| document.getElementById('pickerMode').textContent = currentMode === 'auto' ? 'AUTO' : 'MANUAL'; | |
| const ago = Math.round((Date.now() - cacheInfo.fetchedAt) / 60000); | |
| document.getElementById('cacheNote').textContent = | |
| ago < 1 ? 'CACHED · JUST NOW' : `CACHED · ${ago} MIN AGO`; | |
| } | |
| themeSearchEl.addEventListener('input', renderPicker); | |
| document.querySelectorAll('.mode-row button').forEach(btn => { | |
| btn.addEventListener('click', () => setMode(btn.dataset.mode)); | |
| }); | |
| function setMode(m) { | |
| currentMode = m; | |
| document.querySelectorAll('.mode-row button').forEach(b => | |
| b.setAttribute('aria-selected', b.dataset.mode === m ? 'true' : 'false')); | |
| if (m === 'auto') { | |
| const d = detectTheme(); | |
| log('info', `auto-detect: AccentColor=${d.accent} Canvas=${d.canvas} → ${d.best?.id}`); | |
| if (d.best && d.score < 1e8) { | |
| fakeFetch(d.best.id).then(t => applyTheme(t, { | |
| source: `auto-detected via AccentColor+Canvas`, fetched: Date.now() | |
| })).catch(()=>{}); | |
| } | |
| } | |
| renderPicker(); | |
| renderDiagnostics(); | |
| } | |
| /* Grayscale toggle */ | |
| const grayBtn = document.getElementById('grayscaleBtn'); | |
| grayBtn.addEventListener('click', () => { | |
| const on = document.documentElement.dataset.grayscale !== 'true'; | |
| if (on) { | |
| document.documentElement.dataset.grayscale = 'true'; | |
| } else { | |
| delete document.documentElement.dataset.grayscale; | |
| } | |
| grayBtn.setAttribute('aria-pressed', on ? 'true' : 'false'); | |
| }); | |
| /* "Fetch live" — simulate a re-fetch of the active theme */ | |
| document.getElementById('fetchLiveBtn').addEventListener('click', async () => { | |
| try { | |
| const t = await fakeFetch(activeTheme.id); | |
| applyTheme(t, { source: `refetched · ${t.author}/omarchy-${t.id}-theme`, fetched: Date.now() }); | |
| } catch {} | |
| }); | |
| /* ========================================================= | |
| DIAGNOSTICS DRAWER | |
| ========================================================= */ | |
| const diagDrawer = document.getElementById('diagDrawer'); | |
| document.getElementById('diagToggle').addEventListener('click', | |
| () => diagDrawer.dataset.open = diagDrawer.dataset.open === 'true' ? 'false' : 'true'); | |
| document.getElementById('diagClose').addEventListener('click', | |
| () => diagDrawer.dataset.open = 'false'); | |
| document.getElementById('openDiag').addEventListener('click', (e) => { | |
| e.preventDefault(); diagDrawer.dataset.open = 'true'; | |
| }); | |
| function renderDiagnostics() { | |
| const d = detectTheme(); | |
| document.getElementById('d-mode').textContent = currentMode; | |
| document.getElementById('d-accent').textContent = d.accent || '(unsupported)'; | |
| document.getElementById('d-canvas').textContent = d.canvas || '(unsupported)'; | |
| document.getElementById('d-pcs').textContent = d.pcs; | |
| document.getElementById('d-detected').textContent = d.best ? `${d.best.id} (Δ${Math.round(d.score)})` : '—'; | |
| document.getElementById('d-theme').textContent = activeTheme.id; | |
| document.getElementById('d-source').textContent = cacheInfo.source; | |
| document.getElementById('d-fetched').textContent = new Date(cacheInfo.fetchedAt).toLocaleTimeString(); | |
| document.getElementById('varsGrid').innerHTML = | |
| Object.entries(activePalette).map(([k,v]) => | |
| `<div class="var-pill"><span class="chip" style="background:${v}"></span><span class="name">${k}</span><span class="val">${v}</span></div>` | |
| ).join(''); | |
| renderExport(); | |
| } | |
| let exportTab = 'css'; | |
| document.querySelectorAll('.code-tabs button').forEach(b => { | |
| b.addEventListener('click', () => { | |
| exportTab = b.dataset.tab; | |
| document.querySelectorAll('.code-tabs button').forEach(x => | |
| x.setAttribute('aria-selected', x.dataset.tab === exportTab ? 'true' : 'false')); | |
| renderExport(); | |
| }); | |
| }); | |
| function renderExport() { | |
| const el = document.getElementById('exportCode'); | |
| if (exportTab === 'css') { | |
| el.textContent = `:root {\n${Object.entries(activePalette).map(([k,v]) => ` ${k}: ${v};`).join('\n')}\n}`; | |
| } else if (exportTab === 'php') { | |
| const slots = [ | |
| ['Contrast', 'contrast', '--contrast'], | |
| ['Contrast 2', 'contrast-2', '--contrast-2'], | |
| ['Contrast 3', 'contrast-3', '--contrast-3'], | |
| ['Base', 'base', '--base'], | |
| ['Base 2', 'base-2', '--base-2'], | |
| ['Base 3', 'base-3', '--base-3'], | |
| ['Accent', 'accent', '--accent'], | |
| ]; | |
| el.textContent = `add_filter( 'option_generate_settings', function( $s ) {\n` | |
| + ` $s['global_colors'] = [\n` | |
| + slots.map(([name, slug, v]) => | |
| ` [ 'name' => __( '${name}', 'generatepress' ), 'slug' => '${slug}', 'color' => '${activePalette[v]}' ],` | |
| ).join('\n') | |
| + `\n ];\n return $s;\n} );`; | |
| } else { | |
| el.textContent = JSON.stringify({ | |
| theme: activeTheme.id, | |
| author: activeTheme.author, | |
| kind: activeTheme.kind, | |
| palette: activePalette, | |
| fetched_at: new Date(cacheInfo.fetchedAt).toISOString(), | |
| }, null, 2); | |
| } | |
| } | |
| document.getElementById('copyBtn').addEventListener('click', () => { | |
| navigator.clipboard.writeText(document.getElementById('exportCode').textContent); | |
| const btn = document.getElementById('copyBtn'); | |
| const old = btn.textContent; btn.textContent = 'Copied'; | |
| setTimeout(() => btn.textContent = old, 1200); | |
| }); | |
| /* ========================================================= | |
| LOG | |
| ========================================================= */ | |
| const logEl = document.getElementById('fetchLog'); | |
| function log(level, msg) { | |
| const t = new Date().toLocaleTimeString(); | |
| const line = document.createElement('div'); | |
| line.className = 'l-' + level; | |
| line.textContent = `[${t}] ${msg}`; | |
| logEl.prepend(line); | |
| } | |
| /* ========================================================= | |
| VIEW SWITCHER + LOGIN SIMULATION | |
| ========================================================= */ | |
| document.querySelectorAll('.view-switch button').forEach(b => { | |
| b.addEventListener('click', () => { | |
| const v = b.dataset.view; | |
| document.querySelectorAll('.view-switch button').forEach(x => | |
| x.setAttribute('aria-current', x.dataset.view === v ? 'true' : 'false')); | |
| document.querySelectorAll('.view').forEach(x => | |
| x.dataset.active = x.dataset.view === v ? 'true' : 'false'); | |
| localStorage.setItem('eo-portal:view', v); | |
| }); | |
| }); | |
| const savedView = localStorage.getItem('eo-portal:view'); | |
| if (savedView) { | |
| document.querySelector(`.view-switch button[data-view="${savedView}"]`)?.click(); | |
| } | |
| /* Login error simulation */ | |
| const alertEl = document.getElementById('loginAlert'); | |
| const alertText = document.getElementById('loginAlertText'); | |
| document.querySelectorAll('.error-simulator button').forEach(b => { | |
| b.addEventListener('click', () => { | |
| const e = b.dataset.err; | |
| if (e === 'clear') { alertEl.style.display = 'none'; return; } | |
| alertText.textContent = e === 'invalid' | |
| ? "Incorrect username or password. Please try again." | |
| : "Incorrect username or password. Please try again."; // identical on purpose | |
| alertEl.style.display = 'flex'; | |
| }); | |
| }); | |
| document.getElementById('loginForm').addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| alertText.textContent = "Incorrect username or password. Please try again."; | |
| alertEl.style.display = 'flex'; | |
| }); | |
| /* ========================================================= | |
| COURSE DATA | |
| ========================================================= */ | |
| const COURSES = [ | |
| { code: 'ME 340', name: 'Heat Transfer', instructor: 'Prof. Helena Park', lectures: 32, watched: 18, latest: 'Lec 18 · Apr 16' }, | |
| { code: 'EE 320', name: 'Electromagnetic Theory', instructor: 'Prof. Daniel Okafor', lectures: 28, watched: 12, latest: 'Lec 12 · Apr 14' }, | |
| { code: 'CS 445', name: 'Compiler Design', instructor: 'Dr. Ayesha Rahman', lectures: 24, watched: 20, latest: 'Lec 20 · Apr 17' }, | |
| { code: 'ME 487', name: 'Finite Element Methods', instructor: 'Prof. Yuki Tanaka', lectures: 30, watched: 9, latest: 'Lec 9 · Apr 10' }, | |
| { code: 'ENGR 501', name: 'Engineering Ethics', instructor: 'Dr. Marcus Webb', lectures: 14, watched: 14, latest: 'Complete' }, | |
| { code: 'MSE 410', name: 'Materials Characterization', instructor: 'Prof. Lena Huang', lectures: 26, watched: 6, latest: 'Lec 6 · Apr 12' }, | |
| ]; | |
| document.getElementById('courseGrid').innerHTML = COURSES.map(c => { | |
| const pct = Math.round(c.watched / c.lectures * 100); | |
| return `<div class="course-card" role="button" tabindex="0"> | |
| <div> | |
| <div class="code">${c.code}</div> | |
| <h3>${c.name}</h3> | |
| <div class="instructor">${c.instructor}</div> | |
| </div> | |
| <div class="progress"><span style="width: ${pct}%;"></span></div> | |
| <div class="latest">${c.latest}</div> | |
| <div class="meta"><span>${c.watched}/${c.lectures} lectures</span><strong>${pct}%</strong></div> | |
| </div>`; | |
| }).join(''); | |
| /* ========================================================= | |
| BOOT | |
| ========================================================= */ | |
| applyTheme(SEED_THEMES[0]); | |
| renderDiagnostics(); | |
| log('info', 'Bundled default: futurism'); | |
| log('info', 'Auto-detection ready (AccentColor + Canvas)'); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment