Skip to content

Instantly share code, notes, and snippets.

@xicubed
Created April 20, 2026 18:39
Show Gist options
  • Select an option

  • Save xicubed/4d2ec3c9030bd0b1c76b386aa9e36278 to your computer and use it in GitHub Desktop.

Select an option

Save xicubed/4d2ec3c9030bd0b1c76b386aa9e36278 to your computer and use it in GitHub Desktop.
Omarchy theme switcher in browser.
<!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 &amp; 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/&lt;author&gt;/omarchy-&lt;theme&gt;-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