Compare commits
4
Commits
48781f35eb
...
1e44758398
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e44758398 | ||
|
|
3b6e35fa62 | ||
|
|
0317358dcf | ||
|
|
54fa7de9b0 |
No files matched your search
@@ -16,3 +16,6 @@ __pycache__/
|
||||
src/build/
|
||||
src/dist/
|
||||
build_log.txt
|
||||
|
||||
# Impeccable review screenshots (regenerated each review)
|
||||
.impeccable/review/
|
||||
@@ -0,0 +1,446 @@
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"generatedAt": "2026-10-22T18:00:00Z",
|
||||
"title": "Design System: G2Team Potential Updater",
|
||||
"extensions": {
|
||||
"colorMeta": {
|
||||
"night": {
|
||||
"role": "neutral",
|
||||
"displayName": "Night Navy",
|
||||
"canonical": "#040b26",
|
||||
"tonalRamp": [
|
||||
"oklch(15% 0.057 267)",
|
||||
"oklch(25% 0.057 267)",
|
||||
"oklch(35% 0.057 267)",
|
||||
"oklch(45% 0.057 267)",
|
||||
"oklch(55% 0.057 267)",
|
||||
"oklch(68% 0.057 267)",
|
||||
"oklch(80% 0.057 267)",
|
||||
"oklch(95% 0.057 267)"
|
||||
]
|
||||
},
|
||||
"panel": {
|
||||
"role": "neutral",
|
||||
"displayName": "Card Navy",
|
||||
"canonical": "#0c1638",
|
||||
"tonalRamp": [
|
||||
"oklch(15% 0.068 268)",
|
||||
"oklch(25% 0.068 268)",
|
||||
"oklch(35% 0.068 268)",
|
||||
"oklch(45% 0.068 268)",
|
||||
"oklch(55% 0.068 268)",
|
||||
"oklch(68% 0.068 268)",
|
||||
"oklch(80% 0.068 268)",
|
||||
"oklch(95% 0.068 268)"
|
||||
]
|
||||
},
|
||||
"panel-line": {
|
||||
"role": "neutral",
|
||||
"displayName": "Card Edge",
|
||||
"canonical": "#26335e",
|
||||
"tonalRamp": [
|
||||
"oklch(15% 0.078 269)",
|
||||
"oklch(25% 0.078 269)",
|
||||
"oklch(35% 0.078 269)",
|
||||
"oklch(45% 0.078 269)",
|
||||
"oklch(55% 0.078 269)",
|
||||
"oklch(68% 0.078 269)",
|
||||
"oklch(80% 0.078 269)",
|
||||
"oklch(95% 0.078 269)"
|
||||
]
|
||||
},
|
||||
"field": {
|
||||
"role": "neutral",
|
||||
"displayName": "Well Navy",
|
||||
"canonical": "#050c28",
|
||||
"tonalRamp": [
|
||||
"oklch(15% 0.059 268)",
|
||||
"oklch(25% 0.059 268)",
|
||||
"oklch(35% 0.059 268)",
|
||||
"oklch(45% 0.059 268)",
|
||||
"oklch(55% 0.059 268)",
|
||||
"oklch(68% 0.059 268)",
|
||||
"oklch(80% 0.059 268)",
|
||||
"oklch(95% 0.059 268)"
|
||||
]
|
||||
},
|
||||
"field-line": {
|
||||
"role": "neutral",
|
||||
"displayName": "Field Edge",
|
||||
"canonical": "#56689c",
|
||||
"tonalRamp": [
|
||||
"oklch(15% 0.085 269)",
|
||||
"oklch(25% 0.085 269)",
|
||||
"oklch(35% 0.085 269)",
|
||||
"oklch(45% 0.085 269)",
|
||||
"oklch(55% 0.085 269)",
|
||||
"oklch(68% 0.085 269)",
|
||||
"oklch(80% 0.085 269)",
|
||||
"oklch(95% 0.085 269)"
|
||||
]
|
||||
},
|
||||
"pill-fill": {
|
||||
"role": "neutral",
|
||||
"displayName": "Button Navy",
|
||||
"canonical": "#0a1435",
|
||||
"tonalRamp": [
|
||||
"oklch(15% 0.066 268)",
|
||||
"oklch(25% 0.066 268)",
|
||||
"oklch(35% 0.066 268)",
|
||||
"oklch(45% 0.066 268)",
|
||||
"oklch(55% 0.066 268)",
|
||||
"oklch(68% 0.066 268)",
|
||||
"oklch(80% 0.066 268)",
|
||||
"oklch(95% 0.066 268)"
|
||||
]
|
||||
},
|
||||
"pill-fill-hover": {
|
||||
"role": "neutral",
|
||||
"displayName": "Button Navy Lifted",
|
||||
"canonical": "#101d45",
|
||||
"tonalRamp": [
|
||||
"oklch(15% 0.077 267)",
|
||||
"oklch(25% 0.077 267)",
|
||||
"oklch(35% 0.077 267)",
|
||||
"oklch(45% 0.077 267)",
|
||||
"oklch(55% 0.077 267)",
|
||||
"oklch(68% 0.077 267)",
|
||||
"oklch(80% 0.077 267)",
|
||||
"oklch(95% 0.077 267)"
|
||||
]
|
||||
},
|
||||
"pill-line": {
|
||||
"role": "neutral",
|
||||
"displayName": "Moonlight Edge",
|
||||
"canonical": "#c9d5f2",
|
||||
"tonalRamp": [
|
||||
"oklch(15% 0.042 268)",
|
||||
"oklch(25% 0.042 268)",
|
||||
"oklch(35% 0.042 268)",
|
||||
"oklch(45% 0.042 268)",
|
||||
"oklch(55% 0.042 268)",
|
||||
"oklch(68% 0.042 268)",
|
||||
"oklch(80% 0.042 268)",
|
||||
"oklch(95% 0.042 268)"
|
||||
]
|
||||
},
|
||||
"pill-text": {
|
||||
"role": "neutral",
|
||||
"displayName": "Pure White",
|
||||
"canonical": "#ffffff",
|
||||
"tonalRamp": [
|
||||
"oklch(15% 0 0)",
|
||||
"oklch(25% 0 0)",
|
||||
"oklch(35% 0 0)",
|
||||
"oklch(45% 0 0)",
|
||||
"oklch(55% 0 0)",
|
||||
"oklch(68% 0 0)",
|
||||
"oklch(80% 0 0)",
|
||||
"oklch(95% 0 0)"
|
||||
]
|
||||
},
|
||||
"ink": {
|
||||
"role": "neutral",
|
||||
"displayName": "Starlight Ink",
|
||||
"canonical": "#eef2ff",
|
||||
"tonalRamp": [
|
||||
"oklch(15% 0.018 272)",
|
||||
"oklch(25% 0.018 272)",
|
||||
"oklch(35% 0.018 272)",
|
||||
"oklch(45% 0.018 272)",
|
||||
"oklch(55% 0.018 272)",
|
||||
"oklch(68% 0.018 272)",
|
||||
"oklch(80% 0.018 272)",
|
||||
"oklch(95% 0.018 272)"
|
||||
]
|
||||
},
|
||||
"ink-2": {
|
||||
"role": "neutral",
|
||||
"displayName": "Mist Ink",
|
||||
"canonical": "#a9b6d8",
|
||||
"tonalRamp": [
|
||||
"oklch(15% 0.051 269)",
|
||||
"oklch(25% 0.051 269)",
|
||||
"oklch(35% 0.051 269)",
|
||||
"oklch(45% 0.051 269)",
|
||||
"oklch(55% 0.051 269)",
|
||||
"oklch(68% 0.051 269)",
|
||||
"oklch(80% 0.051 269)",
|
||||
"oklch(95% 0.051 269)"
|
||||
]
|
||||
},
|
||||
"owl": {
|
||||
"role": "primary",
|
||||
"displayName": "Owl Orange",
|
||||
"canonical": "#ec7d33",
|
||||
"tonalRamp": [
|
||||
"oklch(15% 0.16 50)",
|
||||
"oklch(25% 0.16 50)",
|
||||
"oklch(35% 0.16 50)",
|
||||
"oklch(45% 0.16 50)",
|
||||
"oklch(55% 0.16 50)",
|
||||
"oklch(68% 0.16 50)",
|
||||
"oklch(80% 0.16 50)",
|
||||
"oklch(95% 0.16 50)"
|
||||
]
|
||||
},
|
||||
"owl-soft": {
|
||||
"role": "primary",
|
||||
"displayName": "Lamplight Orange",
|
||||
"canonical": "#f5a25a",
|
||||
"tonalRamp": [
|
||||
"oklch(15% 0.132 60)",
|
||||
"oklch(25% 0.132 60)",
|
||||
"oklch(35% 0.132 60)",
|
||||
"oklch(45% 0.132 60)",
|
||||
"oklch(55% 0.132 60)",
|
||||
"oklch(68% 0.132 60)",
|
||||
"oklch(80% 0.132 60)",
|
||||
"oklch(95% 0.132 60)"
|
||||
]
|
||||
},
|
||||
"error": {
|
||||
"role": "semantic",
|
||||
"displayName": "Soft Alarm",
|
||||
"canonical": "#ff9a9a",
|
||||
"tonalRamp": [
|
||||
"oklch(15% 0.121 20)",
|
||||
"oklch(25% 0.121 20)",
|
||||
"oklch(35% 0.121 20)",
|
||||
"oklch(45% 0.121 20)",
|
||||
"oklch(55% 0.121 20)",
|
||||
"oklch(68% 0.121 20)",
|
||||
"oklch(80% 0.121 20)",
|
||||
"oklch(95% 0.121 20)"
|
||||
]
|
||||
}
|
||||
},
|
||||
"typographyMeta": {
|
||||
"display": {
|
||||
"displayName": "Display",
|
||||
"purpose": "Product name, centered at the top of the card. Single instance."
|
||||
},
|
||||
"title": {
|
||||
"displayName": "Title",
|
||||
"purpose": "Signed-in confirmation heading. Single instance."
|
||||
},
|
||||
"body": {
|
||||
"displayName": "Body",
|
||||
"purpose": "The \"Sign in\" subtitle and running copy, usually in Mist Ink."
|
||||
},
|
||||
"label": {
|
||||
"displayName": "Label",
|
||||
"purpose": "Field labels, 8px above the field."
|
||||
},
|
||||
"button": {
|
||||
"displayName": "Button",
|
||||
"purpose": "Pill button label."
|
||||
},
|
||||
"input": {
|
||||
"displayName": "Input",
|
||||
"purpose": "Typed text; 16px avoids mobile zoom."
|
||||
},
|
||||
"message": {
|
||||
"displayName": "Message",
|
||||
"purpose": "Error and caps-lock lines under the field."
|
||||
}
|
||||
},
|
||||
"shadows": [
|
||||
{
|
||||
"name": "night-lift",
|
||||
"value": "0 18px 40px rgba(0, 3, 15, .5)",
|
||||
"purpose": "The sign-in card only; the single raised surface. Single instance."
|
||||
}
|
||||
],
|
||||
"focusRings": [
|
||||
{
|
||||
"name": "control-focus",
|
||||
"value": "outline: 2px solid #ec7d33; outline-offset: 2px (input, plus border-color #ec7d33), 3px (pill button), 0 (reveal icon button)",
|
||||
"purpose": "Keyboard focus on every interactive element; turns #ff9a9a on an invalid field."
|
||||
}
|
||||
],
|
||||
"motion": [
|
||||
{
|
||||
"name": "control-transition",
|
||||
"value": ".15s ease-out",
|
||||
"purpose": "Pill button border and fill on hover."
|
||||
},
|
||||
{
|
||||
"name": "gaze",
|
||||
"value": "transform .14s ease-out",
|
||||
"purpose": "Owl irises following the caret."
|
||||
},
|
||||
{
|
||||
"name": "blink",
|
||||
"value": "transform .08s ease-out, 160ms, every 2.5-6s; skipped during happy and sad",
|
||||
"purpose": "Owl eyelids."
|
||||
},
|
||||
{
|
||||
"name": "breathe",
|
||||
"value": "3.2s ease-in-out infinite, translateY(.4 SVG units, about 1.6px)",
|
||||
"purpose": "Owl idle bob."
|
||||
},
|
||||
{
|
||||
"name": "droop",
|
||||
"value": "1.4s ease-in-out, head translateY(1.2 units) rotate(-6deg); slow-blink lids 1.4s",
|
||||
"purpose": "Wrong password (sad)."
|
||||
},
|
||||
{
|
||||
"name": "peek",
|
||||
"value": ".15s ease-out on stroke, fill, fill-opacity, filter",
|
||||
"purpose": "Owl eye rims and lenses glow amber while the password is shown."
|
||||
},
|
||||
{
|
||||
"name": "tilt",
|
||||
"value": ".8s ease-in-out, rotate(-10deg)",
|
||||
"purpose": "Empty submit."
|
||||
},
|
||||
{
|
||||
"name": "hop",
|
||||
"value": ".75s cubic-bezier(.3, .6, .35, 1), translateY(-14 units) then -3 rebound; wings flap .75s; smile-eyes .12s ease-out",
|
||||
"purpose": "Successful sign-in (with smile) and tap-the-owl jump (without)."
|
||||
},
|
||||
{
|
||||
"name": "twinkle",
|
||||
"value": "4s ease-in-out infinite, opacity to .15",
|
||||
"purpose": "About a third of the stars."
|
||||
},
|
||||
{
|
||||
"name": "reduced-motion",
|
||||
"value": "prefers-reduced-motion: reduce",
|
||||
"purpose": "All owl and star animation and transitions off, no blink or tap jump, success goes straight to the signed-in panel; gaze and peek glow still apply without easing."
|
||||
}
|
||||
],
|
||||
"breakpoints": [
|
||||
{
|
||||
"name": "narrow",
|
||||
"value": "420px",
|
||||
"purpose": "Owl shrinks from 190px to 160px wide."
|
||||
}
|
||||
],
|
||||
"illustration": {
|
||||
"stars": [
|
||||
"#6ea8ff",
|
||||
"#4fd1c5",
|
||||
"#f29c4b",
|
||||
"#dfe7ff"
|
||||
],
|
||||
"owl": {
|
||||
"viewBox": "0 0 48 50",
|
||||
"outline": "#38200b at 1.2 stroke",
|
||||
"coat": [
|
||||
"#ff990e",
|
||||
"#f58200",
|
||||
"#bd4b00"
|
||||
],
|
||||
"wing": [
|
||||
"#ed7b00",
|
||||
"#a73e00"
|
||||
],
|
||||
"crownFeathers": "#ac4904",
|
||||
"chevrons": "#ffbe42",
|
||||
"faceDisc": "#ffe8b3",
|
||||
"eyeWhite": "#fffdf4",
|
||||
"iris": "#6b3518",
|
||||
"pupil": "#170e08",
|
||||
"glasses": "#2a180c, rounded rectangles 10x6 rx 1.5, sitting low on the face",
|
||||
"beak": "#eb870a",
|
||||
"feet": "#ed900f",
|
||||
"groundGlow": "#ffaa5a at .35 to 0",
|
||||
"peekGlow": {
|
||||
"rimStroke": "#ffc400",
|
||||
"rimGlow": [
|
||||
"#ffd84d",
|
||||
"#ffb300"
|
||||
],
|
||||
"lensFill": "#ffe066 at .45",
|
||||
"lensGlow": "#ffc400"
|
||||
}
|
||||
},
|
||||
"note": "Mascot illustration values only; never applied to UI chrome."
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"name": "Pill Button",
|
||||
"kind": "button",
|
||||
"refersTo": "button-pill",
|
||||
"description": "The dark outlined primary pill, after the user's HOOT button. Full card width. The only button variant in the build.",
|
||||
"html": "<button class=\"ds-pill\">Sign in</button>",
|
||||
"css": ".ds-pill { width: 100%; height: 46px; background: #0a1435; color: #fff; border: 1.5px solid var(--pill-line, #c9d5f2); border-radius: 999px; font: 600 15px \"Segoe UI Variable Text\", \"Segoe UI\", system-ui, -apple-system, sans-serif; letter-spacing: .02em; cursor: pointer; transition: border-color .15s ease-out, background-color .15s ease-out; } .ds-pill:hover { border-color: var(--owl, #ec7d33); background: #101d45; } .ds-pill:active { transform: translateY(1px); } .ds-pill:focus-visible { outline: 2px solid var(--owl, #ec7d33); outline-offset: 3px; } @media (prefers-reduced-motion: reduce) { .ds-pill { transition: none; } }"
|
||||
},
|
||||
{
|
||||
"name": "Pill Password Field",
|
||||
"kind": "input",
|
||||
"refersTo": "input-pill",
|
||||
"description": "Recessed dark pill field with the show/hide eye inset at its right end; error state turns border and outline Soft Alarm.",
|
||||
"html": "<div class=\"ds-wrap\"><label class=\"ds-label\" for=\"ds-pw\">Password</label><div class=\"ds-field\"><input class=\"ds-input\" id=\"ds-pw\" type=\"password\" value=\"hoot\"><button type=\"button\" class=\"ds-reveal\" aria-label=\"Show password\"><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M2.5 12s3.5-6.5 9.5-6.5 9.5 6.5 9.5 6.5-3.5 6.5-9.5 6.5S2.5 12 2.5 12z\"/><circle cx=\"12\" cy=\"12\" r=\"2.8\"/></svg></button></div></div>",
|
||||
"css": ".ds-wrap { width: 312px; color: var(--ink, #eef2ff); font: 15px/1.5 \"Segoe UI Variable Text\", \"Segoe UI\", system-ui, sans-serif; } .ds-label { display: block; font-weight: 600; margin-bottom: 8px; } .ds-field { position: relative; } .ds-input { width: 100%; box-sizing: border-box; height: 46px; padding: 0 52px 0 20px; background: var(--field, #050c28); color: var(--ink, #eef2ff); border: 1px solid var(--field-line, #56689c); border-radius: 999px; font: 16px \"Segoe UI Variable Text\", \"Segoe UI\", system-ui, sans-serif; letter-spacing: .04em; caret-color: var(--owl, #ec7d33); } .ds-input:focus-visible { outline: 2px solid var(--owl, #ec7d33); outline-offset: 2px; border-color: var(--owl, #ec7d33); } .ds-input[aria-invalid=\"true\"] { border-color: var(--error, #ff9a9a); outline-color: var(--error, #ff9a9a); } .ds-reveal { position: absolute; top: 4px; right: 4px; width: 38px; height: 38px; display: grid; place-items: center; padding: 0; background: none; border: 0; border-radius: 50%; color: var(--ink-2, #a9b6d8); cursor: pointer; } .ds-reveal:hover { color: var(--ink, #eef2ff); background: rgba(201, 213, 242, .1); } .ds-reveal:focus-visible { outline: 2px solid var(--owl, #ec7d33); outline-offset: 0; color: var(--ink, #eef2ff); } .ds-reveal svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }"
|
||||
},
|
||||
{
|
||||
"name": "Field Messages",
|
||||
"kind": "custom",
|
||||
"description": "Error line in Soft Alarm and caps-lock note in Lamplight Orange, 14px, 8px under the field.",
|
||||
"html": "<div class=\"ds-msgs\"><p class=\"ds-note\">Caps Lock is on</p><p class=\"ds-error\">That password isn't right. Try again.</p></div>",
|
||||
"css": ".ds-msgs { background: var(--panel, #0c1638); padding: 12px 16px; font: 14px/1.5 \"Segoe UI Variable Text\", \"Segoe UI\", system-ui, sans-serif; } .ds-note, .ds-error { margin: 8px 0 0; } .ds-note { color: var(--owl-soft, #f5a25a); } .ds-error { color: var(--error, #ff9a9a); }"
|
||||
},
|
||||
{
|
||||
"name": "Night Card",
|
||||
"kind": "card",
|
||||
"refersTo": "card",
|
||||
"description": "The single raised surface: Card Navy, 1px Card Edge, 18px corners, night-lift shadow, with the product name and subtitle centered at its top. Single instance.",
|
||||
"html": "<section class=\"ds-card\"><h1 class=\"ds-h1\">G2Team Potential Updater</h1><p class=\"ds-sub\">Sign in</p><h2 class=\"ds-title\">Run screen coming soon</h2><p class=\"ds-body\">You're signed in. This is where you'll start and watch the bot.</p></section>",
|
||||
"css": ".ds-card { width: min(360px, 100%); box-sizing: border-box; padding: 28px 24px 24px; background: var(--panel, #0c1638); border: 1px solid var(--panel-line, #26335e); border-radius: 18px; box-shadow: 0 18px 40px rgba(0, 3, 15, .5); color: var(--ink, #eef2ff); font: 15px/1.5 \"Segoe UI Variable Text\", \"Segoe UI\", system-ui, sans-serif; } .ds-h1 { margin: 0; font-size: clamp(20px, 5vw, 23px); font-weight: 650; line-height: 1.2; letter-spacing: -.01em; text-wrap: balance; text-align: center; } .ds-sub { margin: 4px 0 22px; color: var(--ink-2, #a9b6d8); text-align: center; } .ds-title { margin: 0; font-size: 20px; } .ds-body { margin: 6px 0 0; color: var(--ink-2, #a9b6d8); }"
|
||||
},
|
||||
{
|
||||
"name": "Night Sky",
|
||||
"kind": "custom",
|
||||
"description": "Night Navy ground with small, sparse round stars, about a third twinkling; kept clear of text and the card.",
|
||||
"html": "<div class=\"ds-sky\"><span style=\"left:12%;top:20%;--c:#6ea8ff\"></span><span style=\"left:80%;top:30%;--c:#4fd1c5\"></span><span style=\"left:30%;top:78%;--c:#f29c4b\"></span><span style=\"left:66%;top:84%;--c:#dfe7ff\"></span></div>",
|
||||
"css": ".ds-sky { position: relative; height: 140px; background: var(--night, #040b26); } .ds-sky span { position: absolute; width: 2.5px; height: 2.5px; border-radius: 50%; background: var(--c); opacity: .7; animation: ds-twinkle 4s ease-in-out infinite; } @keyframes ds-twinkle { 50% { opacity: .15; } } @media (prefers-reduced-motion: reduce) { .ds-sky span { animation: none; } }"
|
||||
}
|
||||
],
|
||||
"narrative": {
|
||||
"northStar": "The Owl's Night Watch",
|
||||
"overview": "A calm night sky, one lit card, and a smooth, softly shaded orange owl perched on its top edge who watches you type. Everything around the owl is quiet and dark: deep navy ground, a few small round stars, dark outlined pill controls, and the platform's own UI face. The owl is the only warm thing on the screen and the only thing that moves with purpose; its reactions (eyes following the caret, a sad droop on a wrong password, an amber glow when the password is shown, a hop on success) carry the feedback alongside plain text, never instead of it.\n\nThe world is rendered natural, not game-like. The user explicitly rejected a pixel-art or game look (\"it must not look like a game, be natural\"): shapes are smooth vector curves with radial-gradient shading, stars are soft round dots, motion is short and eased. This is an Operate tool for an ops team, so density stays low and copy stays plain.\n\nThe system has been built on one surface so far (the sign-in page). Anything below described as a single instance has only that one use in the build and should be treated as provisional, not a pattern.",
|
||||
"keyCharacteristics": [
|
||||
"Deep navy night ground with a sparse, seeded field of small round stars, kept clear of text and controls.",
|
||||
"One warm accent, owl orange, reserved for the owl, focus rings, caret, selection and the hover border.",
|
||||
"Dark outlined pill controls for both the field and the button, after the user's HOOT button.",
|
||||
"Platform UI face for every text role, including the product name.",
|
||||
"A smooth shaded owl mascot whose states are the page's expressive feedback; reduced motion turns its movement off."
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"name": "The One Warm Thing Rule",
|
||||
"body": "Orange belongs to the owl. In the UI it appears only as a signal (focus, caret, selection, hover border, the caps-lock note); controls and surfaces stay navy. The owl's ambers and golds stay inside the owl.",
|
||||
"section": "colors"
|
||||
},
|
||||
{
|
||||
"name": "The Clear Sky Rule",
|
||||
"body": "Stars never sit behind text, the owl or the card; they are placed with a 10px keep-out around each and redrawn on resize from a fixed seed so they do not jump.",
|
||||
"section": "colors"
|
||||
},
|
||||
{
|
||||
"name": "The Single Lift Rule",
|
||||
"body": "One raised surface per screen; controls are outlined, not shadowed.",
|
||||
"section": "elevation"
|
||||
}
|
||||
],
|
||||
"dos": [
|
||||
"Do keep the ground Night Navy and let the owl be the only warm, shaded object on screen.",
|
||||
"Do use Owl Orange for focus rings (2px outline) on every interactive element.",
|
||||
"Do build controls as dark outlined pills (999px, 46px tall) for both fields and primary buttons.",
|
||||
"Do say in text whatever the owl expresses; the owl is decoration with feedback, never the only feedback.",
|
||||
"Do give every owl or star animation a reduced-motion path that turns it off.",
|
||||
"Do keep stars small (1.5 to 3px), round, sparse, seeded and clear of text and controls.",
|
||||
"Do use clearly fake sample data in any design, mockup or screenshot (real run output is PHI)."
|
||||
],
|
||||
"donts": [
|
||||
"Don't make it look like a game: no pixel art, stepped edges, chunky pixel fonts or arcade effects (user decision, 22 Oct).",
|
||||
"Don't fill controls with orange; orange is a signal, not a surface.",
|
||||
"Don't add shadows to controls; the card is the only lifted object.",
|
||||
"Don't add a second type family for display; hierarchy comes from weight and size in the platform face."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
version: 1
|
||||
slug: "src-ui-login-html"
|
||||
primary_target: "src/ui/login.html"
|
||||
related_targets: []
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
Operate. G2 sign-in page for the ops team, first build only: owl, password field, show/hide, sign in, test password, placeholder "run screen coming soon" after sign-in. Real password storage, lockout, per-person usernames and the run screen are undecided and out of scope.
|
||||
|
||||
## Direction contract
|
||||
|
||||
THESIS: A sign-in the owl watches. Refuses the faceless centered form with a logo above it: the owl sits on the card and reacts to what you type.
|
||||
|
||||
OWN-WORLD: The user's pinned world, rendered natural, not game-like (user, 22 Oct: no pixel look). Deep navy night ground (#040b26) with small soft round stars (blue, teal, orange, white). A smooth, softly shaded orange owl like the user's reference: dome head with ear tufts, egg body, wide cream face disc, big brown eyes behind round black glasses, peach belly, darker wings, small feet. Dark outlined pill controls, like the user's HOOT button, for both the field and the button. Product name and all text in the platform UI face; standard stroke eye icon.
|
||||
|
||||
STORY: The ops user sees the owl, types the G2 password, and the owl's eyes follow the typing. A wrong password makes the owl droop sadly and blink slowly and the text says what to do. The right one makes it hop and lets them in.
|
||||
|
||||
FIRST VIEWPORT: Centered column. The card with the owl perched on its top edge (feet on the border); inside the card, centered, the product name "G2Team Potential Updater" and "Sign in", then the password field with show/hide and a full-width pill Sign in. The night sky fills the viewport.
|
||||
|
||||
FORM: The user's pinned owl world (their screenshot, then "be natural", not game-like) governs; the roll was run for the record and does not override it. seed:60012e4e290e73f6
|
||||
|
||||
FINISH: unreviewed and undocumented is unfinished; this build ends with the finish review, the verdict, DESIGN.md, and every shipping raster carrying its provenance
|
||||
@@ -0,0 +1,238 @@
|
||||
---
|
||||
name: G2Team Potential Updater
|
||||
description: A local ops tool for running the Charge Billed bot, set in a quiet night sky watched by an orange owl.
|
||||
colors:
|
||||
night: "#040b26"
|
||||
panel: "#0c1638"
|
||||
panel-line: "#26335e"
|
||||
field: "#050c28"
|
||||
field-line: "#56689c"
|
||||
pill-fill: "#0a1435"
|
||||
pill-fill-hover: "#101d45"
|
||||
pill-line: "#c9d5f2"
|
||||
pill-text: "#ffffff"
|
||||
ink: "#eef2ff"
|
||||
ink-2: "#a9b6d8"
|
||||
owl: "#ec7d33"
|
||||
owl-soft: "#f5a25a"
|
||||
error: "#ff9a9a"
|
||||
typography:
|
||||
display:
|
||||
fontFamily: "Segoe UI Variable Text, Segoe UI, system-ui, -apple-system, sans-serif"
|
||||
fontSize: "clamp(20px, 5vw, 23px)"
|
||||
fontWeight: 650
|
||||
lineHeight: 1.2
|
||||
letterSpacing: "-0.01em"
|
||||
title:
|
||||
fontFamily: "Segoe UI Variable Text, Segoe UI, system-ui, -apple-system, sans-serif"
|
||||
fontSize: "20px"
|
||||
fontWeight: 700
|
||||
body:
|
||||
fontFamily: "Segoe UI Variable Text, Segoe UI, system-ui, -apple-system, sans-serif"
|
||||
fontSize: "15px"
|
||||
fontWeight: 400
|
||||
lineHeight: 1.5
|
||||
label:
|
||||
fontFamily: "Segoe UI Variable Text, Segoe UI, system-ui, -apple-system, sans-serif"
|
||||
fontSize: "15px"
|
||||
fontWeight: 600
|
||||
lineHeight: 1.5
|
||||
button:
|
||||
fontFamily: "Segoe UI Variable Text, Segoe UI, system-ui, -apple-system, sans-serif"
|
||||
fontSize: "15px"
|
||||
fontWeight: 600
|
||||
letterSpacing: "0.02em"
|
||||
input:
|
||||
fontFamily: "Segoe UI Variable Text, Segoe UI, system-ui, -apple-system, sans-serif"
|
||||
fontSize: "16px"
|
||||
fontWeight: 400
|
||||
letterSpacing: "0.04em"
|
||||
message:
|
||||
fontFamily: "Segoe UI Variable Text, Segoe UI, system-ui, -apple-system, sans-serif"
|
||||
fontSize: "14px"
|
||||
fontWeight: 400
|
||||
lineHeight: 1.5
|
||||
rounded:
|
||||
card: "18px"
|
||||
pill: "999px"
|
||||
round: "50%"
|
||||
spacing:
|
||||
tight: "8px"
|
||||
control-gap: "20px"
|
||||
card: "24px"
|
||||
components:
|
||||
button-pill:
|
||||
backgroundColor: "{colors.pill-fill}"
|
||||
textColor: "{colors.pill-text}"
|
||||
typography: "{typography.button}"
|
||||
rounded: "{rounded.pill}"
|
||||
height: "46px"
|
||||
width: "100%"
|
||||
button-pill-hover:
|
||||
backgroundColor: "{colors.pill-fill-hover}"
|
||||
input-pill:
|
||||
backgroundColor: "{colors.field}"
|
||||
textColor: "{colors.ink}"
|
||||
typography: "{typography.input}"
|
||||
rounded: "{rounded.pill}"
|
||||
padding: "0 52px 0 20px"
|
||||
height: "46px"
|
||||
width: "100%"
|
||||
icon-button-reveal:
|
||||
textColor: "{colors.ink-2}"
|
||||
rounded: "{rounded.round}"
|
||||
size: "38px"
|
||||
card:
|
||||
backgroundColor: "{colors.panel}"
|
||||
rounded: "{rounded.card}"
|
||||
padding: "28px 24px 24px"
|
||||
width: "min(360px, 100%)"
|
||||
---
|
||||
|
||||
# Design System: G2Team Potential Updater
|
||||
|
||||
## Overview
|
||||
|
||||
**Creative North Star: "The Owl's Night Watch"**
|
||||
|
||||
A calm night sky, one lit card, and a smooth, softly shaded orange owl perched on its top edge who watches you type. Everything around the owl is quiet and dark: deep navy ground, a few small round stars, dark outlined pill controls, and the platform's own UI face. The owl is the only warm thing on the screen and the only thing that moves with purpose; its reactions (eyes following the caret, a sad droop on a wrong password, an amber glow when the password is shown, a hop on success) carry the feedback alongside plain text, never instead of it.
|
||||
|
||||
The world is rendered natural, not game-like. The user explicitly rejected a pixel-art or game look ("it must not look like a game, be natural"): shapes are smooth vector curves with radial-gradient shading, stars are soft round dots, motion is short and eased. This is an Operate tool for an ops team, so density stays low and copy stays plain.
|
||||
|
||||
The system has been built on one surface so far (the sign-in page). Anything below described as a single instance has only that one use in the build and should be treated as provisional, not a pattern.
|
||||
|
||||
**Key Characteristics:**
|
||||
- Deep navy night ground with a sparse, seeded field of small round stars, kept clear of text and controls.
|
||||
- One warm accent, owl orange, reserved for the owl, focus rings, caret, selection and the hover border.
|
||||
- Dark outlined pill controls for both the field and the button, after the user's HOOT button.
|
||||
- Platform UI face for every text role, including the product name.
|
||||
- A smooth shaded owl mascot whose states are the page's expressive feedback; reduced motion turns its movement off.
|
||||
|
||||
## Colors
|
||||
|
||||
A cold, near-monochrome navy ramp lit by a single warm orange; the warmth belongs to the owl.
|
||||
|
||||
### Primary
|
||||
- **Owl Orange** (`owl`): the system's only UI accent. Used for focus outlines on every control, the hover border on the pill button, the text caret, and the text-selection background (with Night as the selected text). It is never a fill for controls.
|
||||
- **Lamplight Orange** (`owl-soft`): the gentler orange for advisory text, currently only the "Caps Lock is on" note (single instance).
|
||||
|
||||
### Neutral
|
||||
- **Night Navy** (`night`): the page ground and the sky. Also the scrollbar track.
|
||||
- **Card Navy** (`panel`): the one raised surface, the sign-in card.
|
||||
- **Card Edge** (`panel-line`): the card's 1px border and the scrollbar thumb.
|
||||
- **Well Navy** (`field`): the input fill, slightly darker than the card so the field reads as recessed.
|
||||
- **Field Edge** (`field-line`): the input's resting 1px border (about 3.4:1 against the field).
|
||||
- **Button Navy** (`pill-fill`) and **Button Navy Lifted** (`pill-fill-hover`): the pill button's fill at rest and on hover. These live as literal values in the button rule, not as custom properties.
|
||||
- **Moonlight Edge** (`pill-line`): the button's 1.5px outline. At 10% alpha it is also the reveal icon's hover wash.
|
||||
- **Starlight Ink** (`ink`): primary text, the product name and input text.
|
||||
- **Mist Ink** (`ink-2`): secondary text (the "Sign in" subtitle, body copy under the signed-in title) and the resting reveal icon.
|
||||
- **Pure White** (`pill-text`): the pill button label only.
|
||||
|
||||
### Semantic
|
||||
- **Soft Alarm** (`error`): error message text and the invalid field's border and focus outline. A light coral chosen to read on navy, not a saturated red.
|
||||
|
||||
### Illustration values (not UI tokens)
|
||||
- **Star field:** stars are drawn by script from four colors (Sky Blue `#6ea8ff`, Teal `#4fd1c5`, Ember `#f29c4b`, Frost `#dfe7ff`), 1.5 to 3px, 0.45 to 0.9 opacity, about a third twinkling.
|
||||
- **Owl palette:** the mascot's own gradients and fills, listed under The Owl in Components, including its peek glow (`#ffc400`, `#ffd84d`, `#ffb300`, `#ffe066`). These live only inside the owl SVG and its state rules; they are never applied to UI chrome.
|
||||
|
||||
### Named Rules
|
||||
**The One Warm Thing Rule.** Orange belongs to the owl. In the UI it appears only as a signal (focus, caret, selection, hover border, the caps-lock note); controls and surfaces stay navy. The owl's ambers and golds stay inside the owl.
|
||||
|
||||
**The Clear Sky Rule.** Stars never sit behind text, the owl or the card; they are placed with a 10px keep-out around each and redrawn on resize from a fixed seed so they do not jump.
|
||||
|
||||
## Typography
|
||||
|
||||
**Display Font:** Segoe UI Variable Text (with Segoe UI, system-ui, -apple-system, sans-serif)
|
||||
**Body Font:** the same stack
|
||||
**Label/Mono Font:** none distinct
|
||||
|
||||
**Character:** One platform face for everything, as the direction contract specifies for this Windows-hosted ops tool. Hierarchy comes from weight and size, not from a second family.
|
||||
|
||||
### Hierarchy
|
||||
- **Display** (650, clamp(20px, 5vw, 23px), 1.2, -0.01em, balanced wrapping): the product name, centered at the top of the card. Single instance.
|
||||
- **Title** (700, 20px): the signed-in confirmation heading. Single instance.
|
||||
- **Body** (400, 15px, 1.5): the "Sign in" subtitle and running copy, usually in Mist Ink.
|
||||
- **Label** (600, 15px): field labels, 8px above the field.
|
||||
- **Button** (600, 15px, 0.02em): the pill button label.
|
||||
- **Input** (400, 16px, 0.04em): typed text; 16px keeps mobile browsers from zooming.
|
||||
- **Message** (400, 14px): the error and caps-lock lines under the field.
|
||||
|
||||
## Layout
|
||||
|
||||
A single centered column on a full-viewport sky. The column is anchored a fixed distance above mid-screen (top padding `max(32px, 50vh - 230px)`, 16px sides, 48px bottom) rather than vertically centered, so a message appearing in the card grows the card downward instead of moving the owl. The card is `min(360px, 100%)` wide, with its top margin reserving the owl's height (owl width × 50/48 + 12px) so the owl perches on the card's top edge with its feet 7px over the border.
|
||||
|
||||
Everything sits inside the card. The product name and the "Sign in" subtitle are centered at its top (subtitle 4px below the name, 22px above the field label); the form below them is left-aligned. After sign-in the subtitle and form hide and the signed-in panel takes their place under the product name.
|
||||
|
||||
Spacing is small and repeated: 8px between label and field and between field and message lines; 20px between the field block and the button; 6px between the signed-in title and its copy; 24px card padding (28px at top).
|
||||
|
||||
One breakpoint: at 420px and below the owl shrinks from 190px to 160px wide; nothing else reflows.
|
||||
|
||||
## Elevation & Depth
|
||||
|
||||
Mostly tonal: the night ground, the slightly lighter card, and the slightly darker recessed field carry the layering. The card is the one lifted object, with a single deep, soft ambient shadow that sinks it into the night rather than floating it. Under the owl a faint orange radial glow (`#ffaa5a` at 35% fading to 0) grounds it on the card. No other element casts a shadow.
|
||||
|
||||
### Shadow Vocabulary
|
||||
- **Night lift** (`box-shadow: 0 18px 40px rgba(0, 3, 15, .5)`): the sign-in card only. Single instance.
|
||||
|
||||
### Named Rules
|
||||
**The Single Lift Rule.** One raised surface per screen; controls are outlined, not shadowed.
|
||||
|
||||
## Shapes
|
||||
|
||||
Full round pills (999px) for both the field and the primary button; the reveal icon button is a 38px circle inset 4px into the field's right end. The card uses a gently rounded 18px corner with a 1px border. The owl is one smooth rounded silhouette with a heart-shaped face disc, oval eyes and softly rounded rectangular glasses (1.5 corner radius in SVG units). Stars are circles. Nothing in the system is sharp-cornered or stepped.
|
||||
|
||||
## Components
|
||||
|
||||
### Buttons
|
||||
Dark, outlined and calm, after the user's HOOT button.
|
||||
- **Shape:** full pill (999px), 46px tall, full card width.
|
||||
- **Primary (pill):** Button Navy fill, Pure White label (600, 15px, 0.02em), 1.5px Moonlight Edge outline, 20px above it.
|
||||
- **Hover:** border turns Owl Orange and the fill lifts to Button Navy Lifted, over 0.15s ease-out.
|
||||
- **Active:** nudges down 1px.
|
||||
- **Focus:** 2px Owl Orange outline, 3px offset.
|
||||
- The only button variant in the build; there is no secondary or ghost button yet.
|
||||
|
||||
### Inputs / Fields
|
||||
- **Style:** Well Navy fill, 1px Field Edge border, full pill (999px), 46px tall, 20px left padding and 52px right padding to clear the reveal button; Starlight Ink text at 16px with 0.04em tracking. Label sits above in Label weight. The password field takes focus on load.
|
||||
- **Focus:** 2px Owl Orange outline at 2px offset and the border also turns Owl Orange.
|
||||
- **Error:** when invalid, the border and focus outline turn Soft Alarm, and a Soft Alarm message (14px) appears 8px below; the message is announced and clears as soon as the user types.
|
||||
- **Caps Lock:** a Lamplight Orange note, "Caps Lock is on", appears below while the key is on and hides on blur.
|
||||
- **Reveal (show/hide password):** a 38px circular icon button inside the field's right end, Mist Ink at rest; on hover Starlight Ink with a 10% Moonlight wash; focus is a 2px Owl Orange outline at 0 offset. The icon is a standard 24-unit stroke eye (20px, 1.8 stroke, round caps and joins) that swaps to eye-off; it does not steal focus from the field. This is the build's only icon.
|
||||
|
||||
### Cards / Containers
|
||||
- **Corner Style:** 18px.
|
||||
- **Background:** Card Navy, 1px Card Edge border.
|
||||
- **Shadow Strategy:** Night lift (see Elevation & Depth).
|
||||
- **Internal Padding:** 28px top, 24px sides and bottom.
|
||||
- Single instance: the sign-in card, which hosts the owl on its top edge and the centered product name and subtitle inside.
|
||||
|
||||
### The Owl (signature component)
|
||||
An inline SVG (viewBox 48×50, 190px wide, 160px at 420px and below), smooth and shaded, every shape outlined in dark brown (`#38200b`, 1.2 stroke, round joins). The coat is a radial gradient (`#ff990e` → `#f58200` → `#bd4b00`), the wings a darker linear gradient (`#ed7b00` → `#a73e00`), with crown feather strokes (`#ac4904`) and rows of small golden chevron feather marks on the breast (`#ffbe42`). A flat cream face disc (`#ffe8b3`) holds oval eyes (whites `#fffdf4`, brown irises `#6b3518`, near-black pupils `#170e08`, white catchlights), a beak (`#eb870a`) and small feet (`#ed900f`). Square, rounded-rectangle glasses in dark brown (`#2a180c`, 1 stroke) sit low on the face across the lower half of the eyes, with a curved bridge and short temple arms; this is the user's preferred look and supersedes the round glasses in the direction contract. It is decorative (`aria-hidden`); every state it shows is also stated in text.
|
||||
|
||||
States:
|
||||
- **Idle:** a breathing bob of 0.4 SVG units (about 1.6px at 190px) over 3.2s, continuous.
|
||||
- **Blink:** eyelids close for 160ms at random 2.5 to 6s intervals; skipped during the success and sad states.
|
||||
- **Gaze:** while the password field has focus, the irises look down and slide from left to right as the typed text (or masked dots) fills the field, tracking the caret; they recenter on blur.
|
||||
- **Empty submit (tilt):** the head tilts -10deg and back over 0.8s.
|
||||
- **Wrong password (sad):** over 1.4s the head droops (down 1.2 units, rotated -6deg) and the eyes close in one slow blink, then the gaze returns to the caret.
|
||||
- **Show password (peek):** while the password is visible, the eye rims glow amber (stroke `#ffc400` at 1.5, drop-shadow glow `#ffd84d` and `#ffb300`) and the glasses lenses fill with a translucent gold (`#ffe066` at 45%, glow `#ffc400`), easing in and out over 0.15s. These four are illustration values of the owl, not UI tokens.
|
||||
- **Tap the owl (jump):** clicking the owl plays the success hop and wing flap without the smile. Pointer-only and purely decorative.
|
||||
- **Success (hop):** a 0.75s hop (14 units, then a 3-unit rebound), both wings flap, and the eyes close into upward smile arcs; then the subtitle and form hide, the signed-in panel replaces them and receives focus.
|
||||
- **Reduced motion:** all owl and star animation and transitions are off, blinking and the tap jump stop, and success goes straight to the signed-in panel. The gaze still repositions and the peek glow still shows, both without easing.
|
||||
|
||||
## Do's and Don'ts
|
||||
|
||||
### Do:
|
||||
- **Do** keep the ground Night Navy and let the owl be the only warm, shaded object on screen.
|
||||
- **Do** use Owl Orange for focus rings (2px outline) on every interactive element.
|
||||
- **Do** build controls as dark outlined pills (999px, 46px tall) for both fields and primary buttons.
|
||||
- **Do** say in text whatever the owl expresses; the owl is decoration with feedback, never the only feedback.
|
||||
- **Do** give every owl or star animation a reduced-motion path that turns it off.
|
||||
- **Do** keep stars small (1.5 to 3px), round, sparse, seeded and clear of text and controls.
|
||||
- **Do** use clearly fake sample data in any design, mockup or screenshot (real run output is PHI).
|
||||
|
||||
### Don't:
|
||||
- **Don't** make it look like a game: no pixel art, stepped edges, chunky pixel fonts or arcade effects (user decision, 22 Oct).
|
||||
- **Don't** fill controls with orange; orange is a signal, not a surface.
|
||||
- **Don't** add shadows to controls; the card is the only lifted object.
|
||||
- **Don't** add a second type family for display; hierarchy comes from weight and size in the platform face.
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
# Product
|
||||
|
||||
<!-- impeccable:product-schema 1 -->
|
||||
|
||||
## Platform
|
||||
|
||||
web
|
||||
|
||||
## Stack
|
||||
|
||||
delegated: a local page served by the bot itself with Python's standard-library `http.server`, bound to `127.0.0.1` and opened in the default browser on the ops user's Windows PC. It ships inside the existing PyInstaller exe (`src/ChargeBilledAutomation.spec`). Chosen because the ops team already runs that exe, starting a run needs a live process (a static HTML file cannot do that), it adds no new dependencies (Flask/Streamlit would), and claim data never leaves the PC. The bot itself stays Python (Selenium, pdfplumber, pandas, openpyxl).
|
||||
|
||||
## Users
|
||||
|
||||
The client's operations team (process owner: Sulthan Shafiq N). Three FTEs do this work by hand today: open Waystar, go account by account, open each claim PDF and copy fields into the Excel report template. With the bot, they start the run themselves and watch it, without the RPA developer and without reading a console window.
|
||||
|
||||
Other people involved but not the primary users: the OTS RPA team (builds and maintains the bot), and managers (Vijayakumari G, Swaminathan) who follow report status.
|
||||
|
||||
## Product Purpose
|
||||
|
||||
The Charge Billed bot, run from the G2Team Potential Updater screen, produces the **Charge Billed Report**: charges by procedure, pulled from Waystar CMS-1500 claim PDFs into Excel, one workbook per account. It exists because the client keeps asking for charge and payment reports by procedure, and capturing them by hand takes three people a long time.
|
||||
|
||||
Success means the ops team can start a run, see where it is, and know at the end which claims are saved, which need a person to check, and which failed, without asking the developer.
|
||||
|
||||
The UI to build first is **run and watch the bot**: start a run, see live progress per account and claim, and see which claims failed or need review and why.
|
||||
|
||||
## Positioning
|
||||
|
||||
The bot reads the PDF's text layer directly (`src/cms1500_text.py`) instead of OCR or manual copying, and it does not guess. Any claim with a missing or inconsistent field gets a plain review note (for example "No Insured ID (box 1a)" or "Line charges 120.00 != box 28 total 140.00") so a person checks only those claims, not every claim.
|
||||
|
||||
## Operating Context
|
||||
|
||||
- Runs on a Windows PC. The bot closes all Chrome windows, starts Chrome with a debug port (9222) and a dedicated profile, attaches Selenium, and logs in to Waystar (login.zirmed.com), including a security question.
|
||||
- Settings come from `Input/input.xlsx` (proxy, portal URL, Waystar login, security answers). The Waystar password rotates monthly.
|
||||
- Date range is fixed to the previous calendar month, so in practice it runs once a month. (Inferred from code; not confirmed as the schedule.)
|
||||
- Steps today: close Chrome → read config → launch Chrome → attach → open Waystar → log in → process all accounts. Within an account it pages through the claims list, downloads each claim PDF, extracts it and saves rows in batches.
|
||||
- Output per run date in `Output/<MM-DD-YYYY>/`: `PDF Folder/<Account (ID)>/<claim>.pdf`, `reports/<Account (ID)>.xlsx`, and a per-account log (Claim ID, Status, Remarks, Timestamp). Error screenshots go in `Screenshots/`.
|
||||
- Runs resume: claims and accounts already in the log are skipped on the next run.
|
||||
- Today all progress is console text tagged `[INFO]`, `[WARN]`, `[ERROR]`, `[SUCCESS]`, with numbered `[STEP n]` lines.
|
||||
- `--selftest <pdfs>` checks a build without Chrome or Waystar and prints counts only.
|
||||
|
||||
## Capabilities and Constraints
|
||||
|
||||
- Confirmed report columns: Claim ID, Rendering Provider #, Signature of Physician, Date of Birth, Insurance Name, Insured ID Number, Date of Service, Diagnosis Codes, CPT, POS, Modifier, Units, Charges, Line Charges, Review Notes.
|
||||
- Terms the ops team uses: account (a practice, shown as "Name (ID)"), claim, CMS-1500 box numbers, Charge Billed Report.
|
||||
- **PHI.** Claim PDFs, reports, logs and screenshots hold patient data (names, DOB, insured IDs). The UI stays on localhost, never sends data to an outside service, and shows counts, claim IDs and review notes rather than patient details unless the task needs them.
|
||||
- **Credentials.** Waystar login and security answers live in `Input/input.xlsx`. The UI must never display them.
|
||||
- Undecided:
|
||||
- Account scope: every Waystar account or only specific ones (asked 10 Mar 2026, no answer). The code currently processes every account in the dropdown.
|
||||
- Payment Report and Charge Reconciliation Report: confirmed as future reports, not started. Optimantra access for them needs a token approved by Ankit Sanghi (pending).
|
||||
- A screen for reviewing and fixing flagged fields: not chosen for now.
|
||||
- Whether a run can be stopped or paused from the UI.
|
||||
|
||||
## Brand Commitments
|
||||
|
||||
The UI is named **G2Team Potential Updater** (user's choice, 22 Oct 2026); the bot and its report keep the name Charge Billed. Mascot: the orange owl with glasses on a navy night sky.
|
||||
|
||||
## Evidence on Hand
|
||||
|
||||
- Real run output from April 2026 in `Output/` (several accounts). It is PHI: never copy it into designs, mockups, demos or screenshots. Use clearly fake sample data instead.
|
||||
- Project history: `../PROJECT_DETAILS.md`, `../Communication_Summary.md`.
|
||||
- Not in the folder: the client's Charge and Payment Excel templates, the PDF field list, and any measure of time saved per report (only the 3-FTE figure is known). Do not invent time-saved numbers, accuracy rates or client quotes.
|
||||
|
||||
## Product Principles
|
||||
|
||||
1. The ops team runs it alone. Every state is readable without the developer or the console.
|
||||
2. Nothing fails silently. Every claim ends as saved, needs review, or failed, with its reason.
|
||||
3. Flag, don't guess. A claim the bot can't read cleanly goes to a person with a plain note.
|
||||
4. Patient data stays on this PC.
|
||||
5. Pick up where it stopped. A rerun continues; it doesn't start over.
|
||||
@@ -0,0 +1,411 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Sign in · G2Team Potential Updater</title>
|
||||
<style>
|
||||
:root {
|
||||
--night: #040b26;
|
||||
--panel: #0c1638;
|
||||
--panel-line: #26335e;
|
||||
--field: #050c28;
|
||||
--field-line: #56689c;
|
||||
--pill-line: #c9d5f2;
|
||||
--ink: #eef2ff;
|
||||
--ink-2: #a9b6d8;
|
||||
--owl: #ec7d33;
|
||||
--owl-soft: #f5a25a;
|
||||
--error: #ff9a9a;
|
||||
--owl-w: 190px;
|
||||
--ui: "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, sans-serif;
|
||||
color-scheme: dark;
|
||||
scrollbar-color: var(--panel-line) var(--night);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html, body { height: 100%; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--night);
|
||||
color: var(--ink);
|
||||
font: 15px/1.5 var(--ui);
|
||||
caret-color: var(--owl);
|
||||
}
|
||||
::selection { background: var(--owl); color: var(--night); }
|
||||
|
||||
.sky { position: fixed; inset: 0; overflow: hidden; pointer-events: none; }
|
||||
.sky span {
|
||||
position: absolute;
|
||||
width: var(--s);
|
||||
height: var(--s);
|
||||
border-radius: 50%;
|
||||
background: var(--c);
|
||||
opacity: var(--o);
|
||||
}
|
||||
.sky .tw { animation: twinkle 4s ease-in-out infinite; animation-delay: var(--d); }
|
||||
@keyframes twinkle { 50% { opacity: .15; } }
|
||||
|
||||
.stage {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
/* Anchored near the middle rather than centered, so an error line grows the card downward instead of moving the owl. */
|
||||
padding: max(32px, calc(50vh - 230px)) 16px 48px;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(20px, 5vw, 23px);
|
||||
font-weight: 650;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -.01em;
|
||||
text-wrap: balance;
|
||||
text-align: center;
|
||||
}
|
||||
.sub { margin: 4px 0 22px; color: var(--ink-2); text-align: center; }
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
width: min(360px, 100%);
|
||||
margin-top: calc(var(--owl-w) * 50 / 48 + 12px);
|
||||
padding: 28px 24px 24px;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--panel-line);
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 18px 40px rgba(0, 3, 15, .5);
|
||||
text-align: left;
|
||||
}
|
||||
.owl {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: calc(100% - 7px);
|
||||
width: var(--owl-w);
|
||||
height: auto;
|
||||
margin-left: calc(var(--owl-w) / -2);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
label { display: block; font-weight: 600; margin-bottom: 8px; }
|
||||
.field { position: relative; }
|
||||
input {
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
padding: 0 52px 0 20px;
|
||||
background: var(--field);
|
||||
color: var(--ink);
|
||||
border: 1px solid var(--field-line);
|
||||
border-radius: 999px;
|
||||
font: 16px var(--ui);
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
input:focus-visible { outline: 2px solid var(--owl); outline-offset: 2px; border-color: var(--owl); }
|
||||
input[aria-invalid="true"] { border-color: var(--error); outline-color: var(--error); }
|
||||
.reveal {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
color: var(--ink-2);
|
||||
cursor: pointer;
|
||||
}
|
||||
.reveal:hover { color: var(--ink); background: rgba(201, 213, 242, .1); }
|
||||
.reveal:focus-visible { outline: 2px solid var(--owl); outline-offset: 0; color: var(--ink); }
|
||||
.reveal svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.8;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.note, .error { margin: 8px 0 0; font-size: 14px; }
|
||||
.note { color: var(--owl-soft); }
|
||||
.error { color: var(--error); }
|
||||
.error:empty { margin: 0; }
|
||||
|
||||
.pill {
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
margin-top: 20px;
|
||||
background: #0a1435;
|
||||
color: #fff;
|
||||
border: 1.5px solid var(--pill-line);
|
||||
border-radius: 999px;
|
||||
font: 600 15px var(--ui);
|
||||
letter-spacing: .02em;
|
||||
cursor: pointer;
|
||||
transition: border-color .15s ease-out, background-color .15s ease-out;
|
||||
}
|
||||
.pill:hover { border-color: var(--owl); background: #101d45; }
|
||||
.pill:active { transform: translateY(1px); }
|
||||
.pill:focus-visible { outline: 2px solid var(--owl); outline-offset: 3px; }
|
||||
|
||||
.done h2 { margin: 0; font-size: 20px; }
|
||||
.done h2:focus { outline: none; }
|
||||
.done p { margin: 6px 0 0; color: var(--ink-2); }
|
||||
|
||||
/* Owl. Origins are in the SVG's own units (viewBox 0 0 48 50). */
|
||||
#bob, #head, #glasses, #wing-l, #wing-r { transform-box: view-box; }
|
||||
#bob { transform-origin: 24px 30px; }
|
||||
#head { transform-origin: 24px 30px; }
|
||||
#glasses { transform-origin: 24px 24px; }
|
||||
#wing-l { transform-origin: 11px 25px; }
|
||||
#wing-r { transform-origin: 37px 25px; }
|
||||
.iris { transition: transform .14s ease-out; }
|
||||
.lid { transform-box: fill-box; transform-origin: 50% 0; transform: scaleY(0); transition: transform .08s ease-out; }
|
||||
.owl.blink .lid { transform: scaleY(1); }
|
||||
.smile { transform-box: fill-box; transform-origin: 50% 100%; transform: scaleY(0); transition: transform .12s ease-out; }
|
||||
.owl.happy .smile { transform: none; }
|
||||
|
||||
.owl.idle #bob { animation: breathe 3.2s ease-in-out infinite; }
|
||||
@keyframes breathe { 50% { transform: translateY(.4px); } }
|
||||
|
||||
.owl.sad #head { animation: droop 1.4s ease-in-out; }
|
||||
@keyframes droop { 20%, 75% { transform: translateY(1.2px) rotate(-6deg); } }
|
||||
.owl.sad .lid { animation: slow-blink 1.4s ease-in-out; }
|
||||
@keyframes slow-blink { 30%, 55% { transform: scaleY(1); } }
|
||||
|
||||
.eye-rim { transition: stroke .15s ease-out, filter .15s ease-out; }
|
||||
.owl.peek .eye-rim { stroke: #ffc400; stroke-width: 1.5; filter: drop-shadow(0 0 2px #ffd84d) drop-shadow(0 0 5px #ffb300); }
|
||||
#glasses rect { transition: fill .15s ease-out, fill-opacity .15s ease-out, filter .15s ease-out; }
|
||||
.owl.peek #glasses rect { fill: #ffe066; fill-opacity: .45; filter: drop-shadow(0 0 3px #ffc400); }
|
||||
|
||||
|
||||
.owl.tilt #head { animation: tilt .8s ease-in-out; }
|
||||
@keyframes tilt { 25%, 70% { transform: rotate(-10deg); } }
|
||||
|
||||
.owl { cursor: pointer; }
|
||||
.owl.happy, .owl.jump { animation: hop .75s cubic-bezier(.3, .6, .35, 1); }
|
||||
@keyframes hop { 40% { transform: translateY(-14px); } 70% { transform: none; } 82% { transform: translateY(-3px); } }
|
||||
.owl.happy #wing-l, .owl.jump #wing-l { animation: flap-l .75s ease-in-out; }
|
||||
.owl.happy #wing-r, .owl.jump #wing-r { animation: flap-r .75s ease-in-out; }
|
||||
@keyframes flap-l { 20%, 60% { transform: rotate(32deg); } 40%, 80% { transform: rotate(8deg); } }
|
||||
@keyframes flap-r { 20%, 60% { transform: rotate(-32deg); } 40%, 80% { transform: rotate(-8deg); } }
|
||||
|
||||
@media (max-width: 420px) {
|
||||
:root { --owl-w: 160px; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.sky .tw, .owl, .owl * { animation: none !important; transition: none !important; }
|
||||
.pill { transition: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sky" id="sky" aria-hidden="true"></div>
|
||||
|
||||
<main class="stage">
|
||||
<section class="card">
|
||||
<svg class="owl idle" id="owl" viewBox="0 0 48 50" aria-hidden="true">
|
||||
<defs>
|
||||
<radialGradient id="coat" cx="42%" cy="28%" r="78%">
|
||||
<stop stop-color="#ff990e"/><stop offset=".6" stop-color="#f58200"/><stop offset="1" stop-color="#bd4b00"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="wing" x2=".8" y2="1">
|
||||
<stop stop-color="#ed7b00"/><stop offset="1" stop-color="#a73e00"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="glow">
|
||||
<stop offset="0" stop-color="#ffaa5a" stop-opacity=".35"/><stop offset="1" stop-color="#ffaa5a" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<clipPath id="eye-l"><ellipse cx="17" cy="20" rx="4" ry="5"/></clipPath>
|
||||
<clipPath id="eye-r"><ellipse cx="31" cy="20" rx="4" ry="5"/></clipPath>
|
||||
</defs>
|
||||
<ellipse cx="24" cy="48.5" rx="17" ry="2.6" fill="url(#glow)"/>
|
||||
<g stroke="#38200b" stroke-width="1.2" stroke-linejoin="round">
|
||||
<path id="wing-l" fill="url(#wing)" d="M10 24 Q5 24 4 32 Q3 38 8 39 L12 34Z"/>
|
||||
<path id="wing-r" fill="url(#wing)" d="M38 24 Q43 24 44 32 Q45 38 40 39 L36 34Z"/>
|
||||
<path fill="url(#coat)" d="M8 27 C8 18 7 11 13 6 Q17 2 24 2 Q31 2 35 6 C41 11 40 18 40 27 Q42 38 35 43 Q30 47 24 47 Q17 47 13 43 Q6 38 8 27Z"/>
|
||||
<path fill="none" stroke="#ffbe42" stroke-width="1.25" stroke-linecap="round"
|
||||
d="M13 33 l1.5 2 l1.5 -2 M19 33 l1.5 2 l1.5 -2 M25 33 l1.5 2 l1.5 -2 M31 33 l1.5 2 l1.5 -2 M16 38 l1.5 2 l1.5 -2 M22 38 l1.5 2 l1.5 -2 M28 38 l1.5 2 l1.5 -2 M20 43 l1.5 1 l1.5 -1 M26 43 l1.5 1 l1.5 -1"/>
|
||||
<path fill="none" stroke="#ac4904" stroke-width="1" d="M12 8 Q18 6 24 14 Q21 6 18 4 M24 13 Q25 6 30 4 M26 13 Q31 7 36 9"/>
|
||||
<g id="bob"><g id="head">
|
||||
<path fill="#ffe8b3" stroke-width="1" d="M24 14 C19 8 11 11 11 18 V26 Q11 30 17 30 Q21 30 24 28 Q27 30 31 30 Q37 30 37 26 V18 C37 11 29 8 24 14Z"/>
|
||||
<g clip-path="url(#eye-l)">
|
||||
<ellipse cx="17" cy="20" rx="4" ry="5" fill="#fffdf4" stroke="none"/>
|
||||
<g class="iris"><ellipse cx="17" cy="21" rx="2.7" ry="3.3" fill="#6b3518" stroke="none"/><ellipse cx="17" cy="21.5" rx="1.8" ry="2.5" fill="#170e08" stroke="none"/><circle cx="16.3" cy="19.5" r="1" fill="#fff" stroke="none"/></g>
|
||||
<rect class="lid" x="13" y="15" width="8" height="10" fill="#ffe8b3" stroke="none"/>
|
||||
<g class="smile"><path fill="#ffe8b3" stroke="none" d="M13 21Q17 16.5 21 21L21 25L13 25Z"/><path fill="none" stroke="#6b3518" stroke-width=".7" stroke-linecap="round" d="M13.6 20.6Q17 17 20.4 20.6"/></g>
|
||||
</g>
|
||||
<ellipse class="eye-rim" cx="17" cy="20" rx="4" ry="5" fill="none" stroke-width="1"/>
|
||||
<g clip-path="url(#eye-r)">
|
||||
<ellipse cx="31" cy="20" rx="4" ry="5" fill="#fffdf4" stroke="none"/>
|
||||
<g class="iris"><ellipse cx="31" cy="21" rx="2.7" ry="3.3" fill="#6b3518" stroke="none"/><ellipse cx="31" cy="21.5" rx="1.8" ry="2.5" fill="#170e08" stroke="none"/><circle cx="30.3" cy="19.5" r="1" fill="#fff" stroke="none"/></g>
|
||||
<rect class="lid" x="27" y="15" width="8" height="10" fill="#ffe8b3" stroke="none"/>
|
||||
<g class="smile"><path fill="#ffe8b3" stroke="none" d="M27 21Q31 16.5 35 21L35 25L27 25Z"/><path fill="none" stroke="#6b3518" stroke-width=".7" stroke-linecap="round" d="M27.6 20.6Q31 17 34.4 20.6"/></g>
|
||||
</g>
|
||||
<ellipse class="eye-rim" cx="31" cy="20" rx="4" ry="5" fill="none" stroke-width="1"/>
|
||||
<g id="glasses" fill="none" stroke="#2a180c" stroke-width="1">
|
||||
<rect x="12" y="22" width="10" height="6" rx="1.5"/>
|
||||
<rect x="26" y="22" width="10" height="6" rx="1.5"/>
|
||||
<path d="M22 23 Q24 21 26 23 M10 21 L12 23 M36 23 L38 21"/>
|
||||
</g>
|
||||
<path fill="#eb870a" stroke-width=".8" d="M22 26 Q24 24 26 26 L24 30Z"/>
|
||||
</g></g>
|
||||
<path fill="#ed900f" stroke-width="1" d="M16 45 L14 48 Q17 49 20 48 L19 46Z M29 46 L28 48 Q31 49 34 48 L32 45Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<h1>G2Team Potential Updater</h1>
|
||||
<p class="sub" id="sub">Sign in</p>
|
||||
|
||||
<form id="login" novalidate>
|
||||
<label for="password">Password</label>
|
||||
<div class="field">
|
||||
<input id="password" name="password" type="password" autocomplete="current-password" autofocus
|
||||
aria-describedby="password-msg caps" required>
|
||||
<button type="button" class="reveal" id="reveal" aria-label="Show password" aria-pressed="false"></button>
|
||||
</div>
|
||||
<p class="note" id="caps" aria-live="polite" hidden>Caps Lock is on</p>
|
||||
<p class="error" id="password-msg" role="alert"></p>
|
||||
<button type="submit" class="pill">Sign in</button>
|
||||
</form>
|
||||
|
||||
<div class="done" id="done" hidden>
|
||||
<h2 id="done-title" tabindex="-1">Run screen coming soon</h2>
|
||||
<p>You're signed in. This is where you'll start and watch the bot.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// ponytail: test password in page source for this demo only; where the real G2 password is kept is still undecided (PRODUCT.md).
|
||||
const TEST_PASSWORD = "hoot2026";
|
||||
|
||||
const EYE = '<path d="M2.5 12s3.5-6.5 9.5-6.5 9.5 6.5 9.5 6.5-3.5 6.5-9.5 6.5S2.5 12 2.5 12z"/><circle cx="12" cy="12" r="2.8"/>';
|
||||
const EYE_OFF = '<path d="M4 4l16 16"/><path d="M9.9 5.7A10 10 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a16 16 0 0 1-2.6 3.4M6.7 7.2C4 9 2.5 12 2.5 12s3.5 6.5 9.5 6.5a9.6 9.6 0 0 0 4.7-1.2"/><path d="M10 10.1a2.8 2.8 0 0 0 3.9 3.9"/>';
|
||||
|
||||
const owl = document.getElementById("owl");
|
||||
const form = document.getElementById("login");
|
||||
const pw = document.getElementById("password");
|
||||
const reveal = document.getElementById("reveal");
|
||||
const caps = document.getElementById("caps");
|
||||
const msg = document.getElementById("password-msg");
|
||||
const done = document.getElementById("done");
|
||||
const irises = owl.querySelectorAll(".iris");
|
||||
const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
|
||||
|
||||
// Seeded so the stars stay put; none behind the text, owl or card.
|
||||
function drawSky() {
|
||||
let seed = 7;
|
||||
const rand = () => (seed = (seed * 16807) % 2147483647) / 2147483647;
|
||||
const colors = ["#6ea8ff", "#4fd1c5", "#f29c4b", "#dfe7ff"];
|
||||
const keepOut = [...document.querySelectorAll(".card, .owl")].map(e => e.getBoundingClientRect());
|
||||
let html = "";
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const tw = rand() < .35, x = rand() * innerWidth, y = rand() * innerHeight;
|
||||
const size = (1.5 + rand() * 1.5).toFixed(1), color = colors[Math.floor(rand() * 4)];
|
||||
const alpha = (.45 + rand() * .45).toFixed(2), delay = (rand() * 4).toFixed(2);
|
||||
if (keepOut.some(r => x > r.left - 10 && x < r.right + 10 && y > r.top - 10 && y < r.bottom + 10)) continue;
|
||||
html += `<span class="${tw ? "tw" : ""}" style="left:${x.toFixed(0)}px;top:${y.toFixed(0)}px;` +
|
||||
`--s:${size}px;--c:${color};--o:${alpha};--d:${delay}s"></span>`;
|
||||
}
|
||||
document.getElementById("sky").innerHTML = html;
|
||||
}
|
||||
drawSky();
|
||||
addEventListener("resize", drawSky);
|
||||
|
||||
function look(dx, dy) {
|
||||
irises.forEach(g => { g.style.transform = `translate(${dx}px, ${dy}px)`; });
|
||||
}
|
||||
|
||||
// Pupils slide from the left end of the field to the right as the typed text grows.
|
||||
const measure = document.createElement("canvas").getContext("2d");
|
||||
function followCaret() {
|
||||
if (document.activeElement !== pw) return look(0, 0);
|
||||
const cs = getComputedStyle(pw);
|
||||
measure.font = cs.font;
|
||||
measure.letterSpacing = cs.letterSpacing;
|
||||
const caret = pw.selectionStart ?? pw.value.length;
|
||||
const shown = pw.type === "password" ? "•".repeat(caret) : pw.value.slice(0, caret);
|
||||
const room = pw.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight);
|
||||
const ratio = Math.min(measure.measureText(shown).width / room, 1);
|
||||
look(-1.2 + 2.4 * ratio, 1.2);
|
||||
}
|
||||
|
||||
const timers = {};
|
||||
function play(name, ms, then) {
|
||||
clearTimeout(timers[name]);
|
||||
owl.classList.remove(name);
|
||||
void owl.getBoundingClientRect();
|
||||
owl.classList.add(name);
|
||||
timers[name] = setTimeout(() => { owl.classList.remove(name); then?.(); }, ms);
|
||||
}
|
||||
|
||||
// Cleared first so a repeated identical message is announced again.
|
||||
function showError(text) {
|
||||
msg.textContent = "";
|
||||
pw.setAttribute("aria-invalid", text ? "true" : "false");
|
||||
if (text) setTimeout(() => { msg.textContent = text; }, 50);
|
||||
}
|
||||
|
||||
function setReveal(show) {
|
||||
pw.type = show ? "text" : "password";
|
||||
reveal.setAttribute("aria-pressed", String(show));
|
||||
reveal.setAttribute("aria-label", show ? "Hide password" : "Show password");
|
||||
owl.classList.toggle("peek", show);
|
||||
reveal.innerHTML = `<svg viewBox="0 0 24 24" aria-hidden="true">${show ? EYE_OFF : EYE}</svg>`;
|
||||
}
|
||||
setReveal(false);
|
||||
|
||||
for (const ev of ["focus", "blur", "input", "keyup", "click", "select"]) pw.addEventListener(ev, followCaret);
|
||||
pw.addEventListener("input", () => { if (msg.textContent) showError(""); });
|
||||
for (const ev of ["keydown", "keyup"]) {
|
||||
pw.addEventListener(ev, e => { caps.hidden = !e.getModifierState?.("CapsLock"); });
|
||||
}
|
||||
pw.addEventListener("blur", () => { caps.hidden = true; });
|
||||
|
||||
// Keep focus (and the owl's gaze) in the field when the eye button is clicked.
|
||||
reveal.addEventListener("mousedown", e => e.preventDefault());
|
||||
reveal.addEventListener("click", () => { setReveal(pw.type === "password"); followCaret(); });
|
||||
|
||||
form.addEventListener("submit", e => {
|
||||
e.preventDefault();
|
||||
if (!pw.value) {
|
||||
showError("Enter your password.");
|
||||
play("tilt", 800);
|
||||
pw.focus();
|
||||
return;
|
||||
}
|
||||
if (pw.value !== TEST_PASSWORD) {
|
||||
showError("That password isn't right. Try again.");
|
||||
pw.focus();
|
||||
pw.select();
|
||||
play("sad", 1400, followCaret);
|
||||
return;
|
||||
}
|
||||
showError("");
|
||||
pw.value = "";
|
||||
setReveal(false);
|
||||
const enter = () => {
|
||||
form.hidden = true;
|
||||
document.getElementById("sub").hidden = true;
|
||||
done.hidden = false;
|
||||
document.getElementById("done-title").focus();
|
||||
};
|
||||
if (reducedMotion.matches) enter();
|
||||
else play("happy", 750, enter);
|
||||
});
|
||||
|
||||
// Tap the owl and it jumps.
|
||||
owl.addEventListener("click", () => { if (!reducedMotion.matches) play("jump", 750); });
|
||||
|
||||
(function blink() {
|
||||
setTimeout(() => {
|
||||
if (!reducedMotion.matches && !owl.classList.contains("happy") && !owl.classList.contains("sad")) play("blink", 160);
|
||||
blink();
|
||||
}, 2500 + Math.random() * 3500);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user