From 93f80150a1d07cf3ce51447636c7f8cd510832f1 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sat, 13 Dec 2025 17:03:24 -0800 Subject: Syntax highlighting and a real repo struct --- src/assets/fonts/Maple.woff2 | Bin 0 -> 73444 bytes src/assets/img/bg.png | Bin 0 -> 47672 bytes src/assets/img/coffee.svg | 1 + src/assets/img/favicon.ico | Bin 0 -> 32243 bytes src/assets/oneko/oneko.gif | Bin 0 -> 3316 bytes src/css/style.css | 1277 ++++++++++++++++++++++++++++++++++++++++++ src/index.html | 599 ++++++++++++++++++++ src/js/oneko.js | 284 ++++++++++ src/js/script.js | 87 +++ 9 files changed, 2248 insertions(+) create mode 100644 src/assets/fonts/Maple.woff2 create mode 100644 src/assets/img/bg.png create mode 100644 src/assets/img/coffee.svg create mode 100644 src/assets/img/favicon.ico create mode 100644 src/assets/oneko/oneko.gif create mode 100644 src/css/style.css create mode 100644 src/index.html create mode 100644 src/js/oneko.js create mode 100644 src/js/script.js (limited to 'src') diff --git a/src/assets/fonts/Maple.woff2 b/src/assets/fonts/Maple.woff2 new file mode 100644 index 0000000..d3641fe Binary files /dev/null and b/src/assets/fonts/Maple.woff2 differ diff --git a/src/assets/img/bg.png b/src/assets/img/bg.png new file mode 100644 index 0000000..ea94170 Binary files /dev/null and b/src/assets/img/bg.png differ diff --git a/src/assets/img/coffee.svg b/src/assets/img/coffee.svg new file mode 100644 index 0000000..6af3eb0 --- /dev/null +++ b/src/assets/img/coffee.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/favicon.ico b/src/assets/img/favicon.ico new file mode 100644 index 0000000..e441116 Binary files /dev/null and b/src/assets/img/favicon.ico differ diff --git a/src/assets/oneko/oneko.gif b/src/assets/oneko/oneko.gif new file mode 100644 index 0000000..a009c2c Binary files /dev/null and b/src/assets/oneko/oneko.gif differ diff --git a/src/css/style.css b/src/css/style.css new file mode 100644 index 0000000..50c6f48 --- /dev/null +++ b/src/css/style.css @@ -0,0 +1,1277 @@ +:root { + /* Win95 "strawberry latte" tokens */ + + /* Desktop (rarely used, but handy for full-page demos) */ + --desktop: #d7a5b7; + + /* Page + surfaces */ + --bg: #f4ece7; /* page behind windows */ + --bg-pattern: #efe5df; + + /* Background texture (dither/checker overlay) */ + --dither-ink: rgba(59, 46, 44, 0.22); + --dither-opacity: 0.18; + --surface: #e9dcd5; /* window face */ + --surface-alt: #fff7f2; /* milk highlight */ + + /* Text */ + --fg: #2a1f1d; + --muted: #6a5550; + + /* 3D border system */ + --border: #3b2e2c; + --border-light: #fff7f2; + --border-dark: #8e7670; + + /* Girly coffee accents */ + --primary: #e56aa6; /* strawberry */ + --primary-light: #f08dbe; /* strawberry milk */ + --primary-dark: #c84d86; /* rose */ + + --secondary: #b69cff; /* lilac */ + --secondary-light: #d7c8ff; + --secondary-dark: #8f78d6; + + --accent-brown: #b88a68; /* latte */ + --accent-brown-light: #d4a574; + --accent-brown-dark: #7a5245; + + /* Title bar (Win95-ish window chrome) */ + --titlebar-a: #d85b9b; + --titlebar-b: #7a3e8e; + --titlebar-fg: #fff7f2; + + /* Links */ + --link: #c84d86; + --link-visited: #6f2f80; + --link-active: #b5173d; + + /* Keep existing names as aliases */ + --accent-pink: var(--primary); + --accent-lavender: var(--secondary); + + /* Status colors (slightly softened) */ + --success: #2e8b57; + --error: #b3261e; + --warning: #b7791f; + --info: #2b6cb0; + + /* Spacing */ + --space-xs: 0.5rem; + --space-sm: 1rem; + --space-md: 1.5rem; + --space-lg: 2rem; + --space-xl: 3rem; + + /* Borders */ + --border-width: 2px; + --border-style: solid; + + /* Type */ + --font-mono: "Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --line-height: 1.35; + + /* Shadows */ + --shadow-sm: 1px 1px 0; + --shadow-md: 2px 2px 0; + --shadow-lg: 3px 3px 0; + --shadow-color: rgba(0, 0, 0, 0.22); + --shadow-box: 2px 2px 0 rgba(0, 0, 0, 0.12); + --shadow-button: 1px 1px 0 rgba(0, 0, 0, 0.18); + + --highlight: rgba(255, 247, 242, 0.9); + --lowlight: rgba(59, 46, 44, 0.55); + + --content-max-width: 1000px; +} + +[data-theme="dark"] { + /* Espresso-night variant */ + --bg: #2d2523; + --bg-pattern: #241e1c; + + --dither-ink: rgba(255, 247, 242, 0.12); + --dither-opacity: 0.14; + --surface: #3b312f; + --surface-alt: #463a37; + + --fg: #f9efea; + --muted: #cbb8b1; + + --border: #f1e6e0; + --border-light: #6e5b57; + --border-dark: #120d0c; + + --primary: #f06aa6; + --primary-light: #ff97c8; + --primary-dark: #d94b8e; + + --secondary: #b69cff; + --secondary-light: #d7c8ff; + --secondary-dark: #8f78d6; + + --accent-brown: #a57353; + --accent-brown-light: #c9936a; + --accent-brown-dark: #6b4a34; + + --titlebar-a: #b04a80; + --titlebar-b: #4f245e; + --titlebar-fg: #fff7f2; + + --link: #ff97c8; + --link-visited: #d7c8ff; + --link-active: #fff0f7; + + /* Status colors (brightened for dark mode) */ + --success: #4caf50; + --error: #ff6b6b; + --warning: #ffa500; + --info: #64b5f6; + + --shadow-color: rgba(0, 0, 0, 0.75); + --highlight: rgba(255, 255, 255, 0.2); + --lowlight: rgba(0, 0, 0, 0.85); +} + +@font-face { + font-family: "Mono"; + src: url("../assets/fonts/Maple.woff2") format("woff2"); + font-display: swap; + font-weight: normal; +} + +@supports (font-synthesis: none) { + @font-face { + font-family: "Mono"; + src: url("../assets/fonts/Maple.woff2") format("woff2"); + font-synthesis: none; + } +} + +/* base */ + +* { + box-sizing: border-box; + margin: 0; + padding: 0; + cursor: + url('data:image/svg+xml;utf8,') + 0 0, + auto !important; +} + +::selection { + background: var(--primary); + color: var(--surface-alt); +} + +:focus-visible { + outline: 1px dotted var(--fg); + outline-offset: 2px; +} + +html { + font-size: 16px; + background-color: var(--bg); + position: relative; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + height: 100vh; +} + +html::before { + content: ""; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: linear-gradient( + 45deg, + var(--dither-ink) 25%, + transparent 25%, + transparent 75%, + var(--dither-ink) 75% + ), + linear-gradient( + 45deg, + var(--dither-ink) 25%, + transparent 25%, + transparent 75%, + var(--dither-ink) 75% + ), + url("../assets/img/bg.png"); + background-size: + 10px 10px, + 10px 10px, + auto; + background-position: + 0 0, + 5px 5px, + 0 0; + background-repeat: repeat; + opacity: var(--dither-opacity); + z-index: -1; + pointer-events: none; +} + +body { + background: transparent; + color: var(--fg); + line-height: var(--line-height); + padding: var(--space-md); + padding-top: calc(var(--space-md) + 3rem); + max-width: var(--content-max-width); + margin: 0 auto; + font-family: var(--font-mono); + scrollbar-color: var(--accent-lavender) var(--bg); + scrollbar-width: thin; + display: flex; + flex-direction: column; + height: 100vh; +} + +/* webkit scrollbar (chrome, safari, edge) */ +::-webkit-scrollbar { + width: 12px; +} + +::-webkit-scrollbar-track { + background: var(--bg); + border: 2px solid var(--border); + box-shadow: + inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark); +} + +::-webkit-scrollbar-thumb { + background: var(--accent-lavender); + border: 2px outset var(--border-light); + box-shadow: + inset 1px 1px 0 var(--highlight), + inset -1px -1px 0 var(--lowlight); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--accent-pink); +} + +.div-centered { + max-width: var(--content-max-width); + margin: auto; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + min-height: calc(100vh - 4.5rem); + text-align: justify; +} + +/* typography */ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--font-mono); + font-weight: 700; + line-height: 1.15; + margin-bottom: var(--space-md); + color: var(--fg); +} + +h1 { + font-size: 2rem; + margin-bottom: var(--space-lg); + background: linear-gradient(90deg, var(--titlebar-a), var(--titlebar-b)); +} + +/* "Titlebar" headings: retro, but still cute */ +h1, +h2, +h3 { + padding: 0.55rem 0.75rem; + border: var(--border-width) solid var(--border); + box-shadow: + inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark); + color: var(--titlebar-fg); + text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25); +} + +h2 { + font-size: 1.5rem; + background: linear-gradient(90deg, var(--titlebar-a), var(--titlebar-b)); +} + +h3 { + font-size: 1.25rem; + background: linear-gradient(90deg, var(--accent-brown-dark), var(--accent-brown)); +} + +h4 { + font-size: 1.1rem; +} + +h5 { + font-size: 1rem; +} + +h6 { + font-size: 0.9rem; +} + +p { + margin-bottom: var(--space-sm); +} + +a { + color: var(--link); + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: 2px; +} + +a:visited { + color: var(--link-visited); +} + +a:hover { + background: var(--primary-light); + color: var(--fg); +} + +a:active { + color: var(--link-active); +} + +small { + color: var(--muted); + font-size: 0.875rem; +} + +blockquote { + border-left: 3px solid var(--accent-brown); + padding-left: var(--space-md); + margin: var(--space-md) 0; + font-style: italic; + color: var(--muted); +} + +/* lists */ + +ul, +ol { + margin: var(--space-sm) 0; + padding-left: var(--space-lg); +} + +li { + margin-bottom: var(--space-xs); +} + +/* tables */ + +table { + width: 100%; + border-collapse: collapse; + margin: var(--space-md) 0; + border: var(--border-width) solid var(--border); + background: var(--surface); + box-shadow: + inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark), + 3px 3px 0 rgba(0, 0, 0, 0.12); +} + +thead { + background: linear-gradient(90deg, var(--titlebar-a), var(--titlebar-b)); + color: var(--titlebar-fg); + font-weight: 700; +} + +th, +td { + padding: var(--space-xs) var(--space-sm); + border: 1px solid var(--border-dark); + text-align: left; +} + +tbody tr:nth-child(odd) { + background: var(--surface-alt); +} + +tbody tr:nth-child(even) { + background: var(--surface); +} + +tbody tr:hover { + background: color-mix(in srgb, var(--primary) 12%, transparent); +} + +/* code */ + +code { + background: var(--surface-alt); + padding: 0.25rem 0.5rem; + border: 1px solid var(--border-dark); + font-size: 0.875rem; + font-family: var(--font-mono); +} + +pre { + background: var(--surface-alt); + border: var(--border-width) solid var(--border); + padding: var(--space-md); + margin: var(--space-md) 0; + overflow-x: auto; + box-shadow: + inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark), + 3px 3px 0 rgba(0, 0, 0, 0.12); +} + +pre code { + background: transparent; + border: none; + padding: 0; + font-size: 0.875rem; +} + +/* Prism.js syntax highlighting */ + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: var(--muted); + font-style: italic; +} + +.token.punctuation { + color: var(--fg); +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol { + color: var(--secondary-dark); +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin { + color: var(--accent-brown-dark); +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: var(--primary-dark); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: var(--primary-dark); + font-weight: 700; +} + +.token.function, +.token.class-name { + color: var(--primary); + font-weight: 700; +} + +.token.regex, +.token.important, +.token.variable { + color: var(--accent-brown); +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +/* Dark theme variants */ + +[data-theme="dark"] .token.keyword, +[data-theme="dark"] .token.operator { + color: var(--primary-light); +} + +[data-theme="dark"] .token.string, +[data-theme="dark"] .token.selector { + color: var(--accent-brown-light); +} + +[data-theme="dark"] .token.number, +[data-theme="dark"] .token.constant { + color: var(--secondary-light); +} + +/* dividers */ + +hr { + border: none; + border-top: 2px solid var(--border-dark); + margin: var(--space-lg) 0; + height: 0; +} + +/* layout */ + +header { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + border-bottom: 2px solid var(--border); + padding: 0.45rem var(--space-md); + margin-bottom: 0; + background: linear-gradient(90deg, var(--titlebar-a), var(--titlebar-b)); + color: var(--titlebar-fg); + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-md); + box-shadow: + inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark), + 0 2px 0 rgba(0, 0, 0, 0.08); +} + +header h1 { + display: none; +} + +header::before { + content: ""; + display: inline-block; + width: 1.5rem; + height: 1.5rem; + background-image: url("../assets/img/coffee.svg"); + background-size: 1.1rem 1.1rem; + background-position: center; + background-repeat: no-repeat; + margin-right: 0.5rem; + background-color: var(--surface); + border: 2px outset var(--border-light); + box-shadow: + inset 1px 1px 0 var(--highlight), + inset -1px -1px 0 var(--lowlight); +} + +nav { + display: flex; + gap: var(--space-md); + flex-wrap: wrap; + align-items: center; + margin: 0; + flex: 1; +} + +nav a { + font-size: 0.85rem; + padding: 0.1rem 0.15rem; + color: var(--titlebar-fg); + text-decoration: none; + border-bottom: 2px solid color-mix(in srgb, var(--titlebar-fg) 55%, transparent); +} + +nav a:visited { + color: var(--titlebar-fg); +} + +nav a:hover { + background: color-mix(in srgb, var(--surface-alt) 18%, transparent); + border-bottom-color: var(--primary-light); +} + +nav a:active { + background: color-mix(in srgb, var(--surface-alt) 28%, transparent); + border-bottom-color: var(--primary); +} + +main { + flex: 1; +} + +article { + border: var(--border-width) solid var(--border); + padding: var(--space-md); + margin-bottom: var(--space-lg); + background: var(--surface); + box-shadow: + inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark), + 4px 4px 0 rgba(0, 0, 0, 0.14), + 5px 5px 0 rgba(0, 0, 0, 0.06); +} + +article > :last-child { + margin-bottom: 0; +} + +article h2 { + margin: calc(var(--space-md) * -1) calc(var(--space-md) * -1) var(--space-md); + border-left: none; + border-right: none; + border-top: none; + border-bottom: var(--border-width) solid var(--border); + box-shadow: none; + text-shadow: none; +} + +footer { + border: var(--border-width) solid var(--border); + padding: var(--space-md); + text-align: center; + background: var(--surface); + font-size: 0.875rem; + box-shadow: + inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark), + 4px 4px 0 rgba(0, 0, 0, 0.14), + 5px 5px 0 rgba(0, 0, 0, 0.06); +} + +footer > :last-child { + margin-bottom: 0; +} + +/* components */ + +/* forms */ + +input, +textarea, +select { + font-family: var(--font-mono); + padding: var(--space-xs) var(--space-sm); + border: 2px inset var(--border-light); + background: var(--surface-alt); + color: var(--fg); + font-size: 0.875rem; + box-shadow: + inset 1px 1px 0 var(--lowlight), + inset -1px -1px 0 var(--highlight); + transition: border-color 0.15s, box-shadow 0.15s; + width: 100%; + max-width: 100%; +} + +input::placeholder, +textarea::placeholder, +select::placeholder { + color: var(--muted); + opacity: 0.8; +} + +[data-theme="dark"] input::placeholder, +[data-theme="dark"] textarea::placeholder, +[data-theme="dark"] select::placeholder { + color: #b8b0a0; + opacity: 1; +} + +input:focus, +textarea:focus, +select:focus { + outline: 1px dotted var(--fg); + outline-offset: 2px; + border-color: var(--primary); + box-shadow: + inset 1px 1px 0 var(--lowlight), + inset -1px -1px 0 var(--highlight), + 0 0 0 2px var(--primary); +} + +input:invalid, +textarea:invalid { + border-color: var(--error); +} + +input:invalid:focus, +textarea:invalid:focus { + box-shadow: + inset 1px 1px 0 var(--highlight), + inset -1px -1px 0 var(--lowlight), + 0 0 0 2px var(--error); +} + +textarea { + resize: vertical; + min-height: 4rem; + width: 100%; +} + +select { + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232a1f1d' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.5rem center; + background-size: 1.2em; + padding-right: 2.5rem; +} + +[data-theme="dark"] select { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f9efea' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); +} + +option { + background: var(--bg); + color: var(--fg); + padding: var(--space-sm); +} + +option:checked { + background: linear-gradient(var(--primary), var(--primary)); + color: var(--bg); +} + +input[type="checkbox"], +input[type="radio"] { + appearance: none; + width: 1.3rem; + height: 1.3rem; + min-width: 1.3rem; + min-height: 1.3rem; + padding: 0; + cursor: pointer; + margin: 0 0.5rem 0 0; + vertical-align: middle; + border: 2px inset var(--border-light); + box-shadow: + inset 1px 1px 0 var(--lowlight), + inset -1px -1px 0 var(--highlight); + background: var(--surface-alt); + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + font-weight: normal; +} + +input[type="radio"] { + border-radius: 50%; +} + +input[type="checkbox"]:checked, +input[type="radio"]:checked { + background: var(--primary); + border-style: inset; +} + +label { + display: block; + margin-bottom: var(--space-xs); + font-weight: bold; + color: var(--fg); +} + +input[type="checkbox"] + label, +input[type="radio"] + label { + display: inline; + margin: 0; + font-weight: normal; +} + +/* toggle switches */ + +input[type="checkbox"].toggle { + appearance: none; + width: 2.8rem; + height: 1.6rem; + padding: 0; + margin: 0 0.5rem 0 0; + border: 2px outset var(--border-light); + background: var(--muted); + cursor: pointer; + position: relative; + vertical-align: middle; + box-shadow: + inset 1px 1px 0 rgba(255, 255, 255, 0.3), + inset -1px -1px 0 rgba(0, 0, 0, 0.6); + transition: background 0.2s; +} + +input[type="checkbox"].toggle:checked { + background: var(--primary); +} + +input[type="checkbox"].toggle::after { + content: ""; + position: absolute; + width: 0.6rem; + height: 0.6rem; + background: var(--surface); + border: 2px outset var(--border-light); + top: 50%; + transform: translateY(-50%); + left: 0.2rem; + transition: left 0.2s; + box-shadow: + inset 1px 1px 0 var(--highlight), + inset -1px -1px 0 var(--lowlight); +} + +input[type="checkbox"].toggle:checked::after { + left: 1.5rem; +} + +[data-theme="dark"] input[type="checkbox"].toggle { + background: var(--secondary); +} + +[data-theme="dark"] input[type="checkbox"].toggle:checked { + background: var(--primary); +} + +[data-theme="dark"] input[type="checkbox"].toggle::after { + background: var(--accent-brown-light); +} + +#theme-toggle::before { + content: "☾"; + position: absolute; + right: 0.4rem; + top: 50%; + transform: translateY(-50%); + font-size: 0.9rem; + line-height: 1; + color: var(--titlebar-fg); + transition: opacity 0.2s; +} + +#theme-toggle:checked::before { + content: "☀"; + right: auto; + left: 0.4rem; +} + +/* range sliders */ + +input[type="range"] { + width: 100%; + height: 2.2rem; + background: transparent; + cursor: pointer; + appearance: none; + vertical-align: middle; +} + +input[type="range"]::-webkit-slider-thumb { + appearance: none; + width: 1.5rem; + height: 1rem; + margin-top: -0.25rem; + background: linear-gradient(135deg, var(--primary-light) 0%, var(--primary) 50%, var(--primary-dark) 100%); + border: 2px outset var(--border-light); + cursor: pointer; + box-shadow: + inset 1px 1px 0 var(--highlight), + inset -1px -1px 0 var(--lowlight), + 2px 2px 0 rgba(0, 0, 0, 0.3), + 3px 3px 0 rgba(0, 0, 0, 0.1); +} + +input[type="range"]::-webkit-slider-thumb:active { + box-shadow: + inset 1px 1px 0 var(--lowlight), + inset -1px -1px 0 var(--highlight), + 1px 1px 0 rgba(0, 0, 0, 0.2); +} + +input[type="range"]::-moz-range-thumb { + width: 1.5rem; + height: 0.8rem; + margin-top: -0.15rem; + background: linear-gradient(135deg, var(--primary-light) 0%, var(--primary) 50%, var(--primary-dark) 100%); + border: 2px outset var(--border-light); + cursor: pointer; + box-shadow: + inset 1px 1px 0 var(--highlight), + inset -1px -1px 0 var(--lowlight), + 2px 2px 0 rgba(0, 0, 0, 0.3), + 3px 3px 0 rgba(0, 0, 0, 0.1); + border-radius: 0.3rem; +} + +input[type="range"]::-moz-range-thumb:active { + box-shadow: + inset 1px 1px 0 var(--lowlight), + inset -1px -1px 0 var(--highlight), + 1px 1px 0 rgba(0, 0, 0, 0.2); +} + +input[type="range"]::-webkit-slider-runnable-track { + background: linear-gradient(180deg, + #c0c0c0 0%, + #e8e8e8 1px, + #dfdfdf 2px, + #d0d0d0 100%); + border: 2px inset var(--border-light); + height: 0.8rem; + box-shadow: + inset 2px 2px 0 rgba(255, 255, 255, 0.8), + inset -2px -2px 0 rgba(0, 0, 0, 0.4), + inset 1px 1px 0 rgba(0, 0, 0, 0.1); +} + +input[type="range"]::-moz-range-track { + background: transparent; + border: none; +} + +input[type="range"]::-moz-range-progress { + background: linear-gradient(180deg, + var(--secondary-light) 0%, + var(--secondary) 50%, + var(--secondary-dark) 100%); + height: 0.8rem; + border: 2px inset var(--border-light); + box-shadow: + inset 2px 2px 0 rgba(255, 255, 255, 0.6), + inset -2px -2px 0 rgba(0, 0, 0, 0.3); +} + +fieldset { + border: 2px solid var(--border-light); + padding: var(--space-md); + margin: var(--space-md) 0; + background: var(--surface); + box-shadow: + inset 1px 1px 0 rgba(255, 255, 255, 0.3), + inset -1px -1px 0 rgba(0, 0, 0, 0.6); +} + +fieldset > :last-child { + margin-bottom: 0; +} + +legend { + padding: 0 var(--space-xs); + margin-left: calc(var(--space-xs) * -1); + font-weight: bold; + color: var(--fg); + background: var(--surface); +} + +/* buttons */ + +button { + font-family: var(--font-mono); + font-weight: 700; + padding: var(--space-xs) var(--space-sm); + border: 2px outset var(--border-light); + background: var(--surface); + color: var(--fg); + font-size: 0.875rem; + box-shadow: + inset 1px 1px 0 var(--highlight), + inset -1px -1px 0 var(--lowlight), + 2px 2px 0 rgba(0, 0, 0, 0.18); + transition: + transform 0.05s, + box-shadow 0.05s, + background 0.1s; + cursor: pointer; +} + +button:hover { + background: var(--surface-alt); +} + +button:active { + transform: translate(1px, 1px); + border-style: inset; + box-shadow: + inset 1px 1px 0 var(--lowlight), + inset -1px -1px 0 var(--highlight); +} + +/* button variants */ + +button.primary { + background: var(--primary); + color: var(--surface-alt); +} + +button.primary:hover { + background: var(--primary-light); +} + +button.primary:active { + background: var(--primary-dark); +} + +button.secondary { + background: var(--secondary); + color: var(--fg); +} + +button.secondary:hover { + background: var(--secondary-light); +} + +button.secondary:active { + background: var(--secondary-dark); +} + +button.success, +button.error, +button.warning, +button.info { + color: var(--surface-alt); +} + +button.success { + background: var(--success); +} + +button.error { + background: var(--error); +} + +button.warning { + background: var(--warning); +} + +button.info { + background: var(--info); +} + +button.success:hover, +button.error:hover, +button.warning:hover, +button.info:hover { + filter: brightness(1.06); +} + +button.contrast { + background: var(--fg); + color: var(--surface-alt); +} + +button.contrast:hover { + background: var(--muted); +} + +button:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none !important; +} + +/* button groups */ + +.button-group { + display: inline-flex; + border: var(--border-width) solid var(--border); + box-shadow: + inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark), + 3px 3px 0 rgba(0, 0, 0, 0.12); +} + +.button-group button { + border: none; + margin: 0; + border-radius: 0; + box-shadow: none; +} + +.button-group button:not(:last-child) { + border-right: 1px solid var(--border-dark); +} + +.button-group button:hover { + box-shadow: none; + transform: none; +} + +.button-group button:active { + box-shadow: none; + transform: none; +} + +/* alerts & messages */ + +.alert { + padding: var(--space-md); + margin: var(--space-md) 0; + border: var(--border-width) solid var(--border); + border-left: 4px solid var(--info); + background: var(--surface); + box-shadow: + inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark), + 3px 3px 0 rgba(0, 0, 0, 0.12); +} + +.alert.success { + border-left-color: var(--success); + background-color: color-mix(in srgb, var(--success) 10%, transparent); +} + +.alert.error { + border-left-color: var(--error); + background-color: color-mix(in srgb, var(--error) 10%, transparent); +} + +.alert.warning { + border-left-color: var(--warning); + background-color: color-mix(in srgb, var(--warning) 10%, transparent); +} + +.alert.info { + border-left-color: var(--info); + background-color: color-mix(in srgb, var(--info) 10%, transparent); +} + +/* badges & pills */ + +.badge { + display: inline-block; + padding: 0.25rem 0.5rem; + margin: 0 0.25rem; + background: var(--surface-alt); + color: var(--fg); + font-size: 0.75rem; + font-weight: 700; + border: 1px solid var(--border-dark); + box-shadow: + inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark); +} + +.badge.primary { + background: var(--primary); + color: var(--surface-alt); +} + +.badge.success { + background: var(--success); + color: var(--surface-alt); +} + +.badge.error { + background: var(--error); + color: var(--surface-alt); +} + +.badge.warning { + background: var(--warning); + color: var(--surface-alt); +} + +.badge.info { + background: var(--info); + color: var(--surface-alt); +} + +.badge.contrast { + background: var(--fg); + color: var(--surface-alt); +} + +/* helpers */ + +.text-success { + color: var(--success); + font-weight: bold; +} + +.text-error { + color: var(--error); + font-weight: bold; +} + +.text-warning { + color: var(--warning); + font-weight: bold; +} + +.text-info { + color: var(--info); + font-weight: bold; +} + +.text-muted { + color: var(--muted); +} + +.text-contrast { + color: var(--fg); + font-weight: bold; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +.text-left { + text-align: left; +} + +.mt-xs { margin-top: var(--space-xs); } +.mt-sm { margin-top: var(--space-sm); } +.mt-md { margin-top: var(--space-md); } +.mt-lg { margin-top: var(--space-lg); } +.mt-xl { margin-top: var(--space-xl); } + +.mb-xs { margin-bottom: var(--space-xs); } +.mb-sm { margin-bottom: var(--space-sm); } +.mb-md { margin-bottom: var(--space-md); } +.mb-lg { margin-bottom: var(--space-lg); } +.mb-xl { margin-bottom: var(--space-xl); } + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.py-md { + padding-top: var(--space-md); + padding-bottom: var(--space-md); +} + +#theme-toggle { + margin-left: auto; + margin-bottom: 0; +} + +/* fairy dust */ + +@keyframes fairy-float { + 0% { + opacity: 1; + transform: translateY(0) scale(1); + } + 100% { + opacity: 0; + transform: translateY(40px) scale(0.5); + } +} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..c5f83a2 --- /dev/null +++ b/src/index.html @@ -0,0 +1,599 @@ + + + + + + Liz CSS - Framework Demo + + + + + +
+ + +
+ +
+ +
+

Color Palette

+

A retro-themed minimal CSS framework with carefully selected colors for light and dark modes.

+ +

Primary Colors

+
+
+ Primary +
+
+ #e56aa6 +
+
+
+ Primary Light +
+
+ #f08dbe +
+
+
+ Primary Dark +
+
+ #c84d86 +
+
+
+ +

Secondary Colors

+
+
+ Secondary +
+
+ #b69cff +
+
+
+ Secondary Light +
+
+ #d7c8ff +
+
+
+ Secondary Dark +
+
+ #8f78d6 +
+
+
+ +

Status Colors

+
+
+ Success +
+
+ #2e8b57 +
+
+
+ Error +
+
+ #b3261e +
+
+
+ Warning +
+
+ #b7791f +
+
+
+ Info +
+
+ #2b6cb0 +
+
+
+
+ + +
+

Buttons

+

Retro-styled buttons with multiple variants and states.

+ +

Basic Buttons

+
+ + + + +
+ +

Status Buttons

+
+ + + + +
+ +

Button States

+
+ + +
+
+ + +
+

Forms

+

Complete form styling with retro aesthetics.

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ Checkboxes +
+ + +
+
+ + +
+
+ +
+ Radio Buttons +
+ + +
+
+ + +
+
+ +
+ + + +
+
+
+ + +
+

Alerts

+

Status messages and notifications with semantic colors.

+ +
+ ℹ️ Info: This is an informational message. +
+ +
+ ✓ Success: Operation completed successfully! +
+ +
+ ⚠️ Warning: Please review this before proceeding. +
+ +
+ ✗ Error: Something went wrong. Please try again. +
+
+ + +
+

Badges & Pills

+

Small inline elements for labels, tags, and status indicators.

+ +

Default Badges

+

+ Default + Primary + Secondary + Contrast +

+ +

Status Badges

+

+ ✓ Success + ✗ Error + ⚠️ Warning + ℹ️ Info +

+
+ + +
+

Typography & Helper Classes

+

Text utilities and spacing helpers for quick styling.

+ +

Text Colors

+

+ Default text (muted) + success text + error text + warning text + info text + contrast text +

+ +

Text Alignment

+

Left aligned

+

Center aligned

+

Right aligned

+ +

Spacing Helpers

+

+ Use .mt-xs, .mt-sm, .mt-md, + .mt-lg, .mt-xl for margin-top. +

+

+ Use .mb-xs, .mb-sm, .mb-md, + .mb-lg, .mb-xl for margin-bottom. +

+

+ Use .mx-auto for horizontal centering and + .py-md for vertical padding. +

+ +
+

Example: Component with margin and padding utilities

+
+
+ + +
+

Heading Styles

+

Heading 1

+

Heading 2

+

Heading 3

+

Heading 4

+
Heading 5
+
Heading 6
+
+ + +
+

Lists

+ +

Unordered List

+
    +
  • Item one with some text
  • +
  • Item two with more text
  • +
  • Item three with even more text
  • +
  • Nested lists: +
      +
    • Sub-item one
    • +
    • Sub-item two
    • +
    +
  • +
+ +

Ordered List

+
    +
  1. First step in the process
  2. +
  3. Second step follows naturally
  4. +
  5. Third step completes the sequence
  6. +
+
+ + +
+

Blockquotes

+
+ "The best time to plant a tree was 20 years ago. The second best time is now." +
+ — Chinese Proverb +
+
+ + +
+

Links

+

+ This is a hyperlink styled with the framework. Links + have a bottom border and change color on hover. You can use them + inline within text or as standalone elements. +

+
+ + +
+

Tables

+

Tables with retro styling, alternating row colors, and hover effects.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureLight ModeDark Mode
BackgroundWarm creamWarm taupe
ButtonsLavender primaryRose primary
BordersRetro 90s beveledRetro 90s beveled
AccentsPink & brownPink & brown
+
+ + +
+

Code

+

Inline const x = 42; looks different from block code:

+
function retro() {
+  return "That's totally rad!";
+}
+
+retro(); // "That's totally rad!"
+
+ + +
+

Dividers

+

Use <hr> to separate content sections:

+
+

Content after the divider looks fresh and organized.

+
+ + +
+

Form Validation

+

Invalid inputs show a red border when they fail validation.

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+

Button Groups

+

Combine buttons into a group with the .button-group class:

+
+ + + +
+
+ + +
+

Toggle Switches

+

Use checkboxes with the .toggle class to create stylish toggle switches:

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

Range Sliders

+

Native range inputs styled with retro beveled appearance:

+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + + + + + diff --git a/src/js/oneko.js b/src/js/oneko.js new file mode 100644 index 0000000..dcf927f --- /dev/null +++ b/src/js/oneko.js @@ -0,0 +1,284 @@ +// oneko.js: https://github.com/adryd325/oneko.js + +export function initOneko() { + const isReducedMotion = + window.matchMedia(`(prefers-reduced-motion: reduce)`) === true || + window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true; + + if (isReducedMotion) return; + + const nekoEl = document.createElement("div"); + let persistPosition = true; + + let nekoPosX = 32; + let nekoPosY = 32; + + let mousePosX = 0; + let mousePosY = 0; + + let frameCount = 0; + let idleTime = 0; + let idleAnimation = null; + let idleAnimationFrame = 0; + + const nekoSpeed = 10; + const spriteSets = { + idle: [[-3, -3]], + alert: [[-7, -3]], + scratchSelf: [ + [-5, 0], + [-6, 0], + [-7, 0], + ], + scratchWallN: [ + [0, 0], + [0, -1], + ], + scratchWallS: [ + [-7, -1], + [-6, -2], + ], + scratchWallE: [ + [-2, -2], + [-2, -3], + ], + scratchWallW: [ + [-4, 0], + [-4, -1], + ], + tired: [[-3, -2]], + sleeping: [ + [-2, 0], + [-2, -1], + ], + N: [ + [-1, -2], + [-1, -3], + ], + NE: [ + [0, -2], + [0, -3], + ], + E: [ + [-3, 0], + [-3, -1], + ], + SE: [ + [-5, -1], + [-5, -2], + ], + S: [ + [-6, -3], + [-7, -2], + ], + SW: [ + [-5, -3], + [-6, -1], + ], + W: [ + [-4, -2], + [-4, -3], + ], + NW: [ + [-1, 0], + [-1, -1], + ], + }; + + function init() { + let nekoFile = "/oneko/oneko.gif"; + const curScript = document.currentScript; + if (curScript && curScript.dataset.cat) { + nekoFile = curScript.dataset.cat; + } + if (curScript && curScript.dataset.persistPosition) { + if (curScript.dataset.persistPosition === "") { + persistPosition = true; + } else { + persistPosition = JSON.parse( + curScript.dataset.persistPosition.toLowerCase(), + ); + } + } + + if (persistPosition) { + let storedNeko = JSON.parse(window.localStorage.getItem("oneko")); + if (storedNeko !== null) { + nekoPosX = storedNeko.nekoPosX; + nekoPosY = storedNeko.nekoPosY; + mousePosX = storedNeko.mousePosX; + mousePosY = storedNeko.mousePosY; + frameCount = storedNeko.frameCount; + idleTime = storedNeko.idleTime; + idleAnimation = storedNeko.idleAnimation; + idleAnimationFrame = storedNeko.idleAnimationFrame; + nekoEl.style.backgroundPosition = storedNeko.bgPos; + } + } + + nekoEl.id = "oneko"; + nekoEl.ariaHidden = true; + nekoEl.style.width = "32px"; + nekoEl.style.height = "32px"; + nekoEl.style.position = "fixed"; + nekoEl.style.pointerEvents = "none"; + nekoEl.style.imageRendering = "pixelated"; + nekoEl.style.left = `${nekoPosX - 16}px`; + nekoEl.style.top = `${nekoPosY - 16}px`; + nekoEl.style.zIndex = 2147483647; + + nekoEl.style.backgroundImage = `url(${nekoFile})`; + + document.body.appendChild(nekoEl); + + document.addEventListener("mousemove", function (event) { + mousePosX = event.clientX; + mousePosY = event.clientY; + }); + + if (persistPosition) { + window.addEventListener("beforeunload", function (event) { + window.localStorage.setItem( + "oneko", + JSON.stringify({ + nekoPosX: nekoPosX, + nekoPosY: nekoPosY, + mousePosX: mousePosX, + mousePosY: mousePosY, + frameCount: frameCount, + idleTime: idleTime, + idleAnimation: idleAnimation, + idleAnimationFrame: idleAnimationFrame, + bgPos: nekoEl.style.backgroundPosition, + }), + ); + }); + } + + window.requestAnimationFrame(onAnimationFrame); + } + + let lastFrameTimestamp; + + function onAnimationFrame(timestamp) { + // Stops execution if the neko element is removed from DOM + if (!nekoEl.isConnected) { + return; + } + if (!lastFrameTimestamp) { + lastFrameTimestamp = timestamp; + } + if (timestamp - lastFrameTimestamp > 100) { + lastFrameTimestamp = timestamp; + frame(); + } + window.requestAnimationFrame(onAnimationFrame); + } + + function setSprite(name, frame) { + const sprite = spriteSets[name][frame % spriteSets[name].length]; + nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`; + } + + function resetIdleAnimation() { + idleAnimation = null; + idleAnimationFrame = 0; + } + + function idle() { + idleTime += 1; + + // every ~ 20 seconds + if ( + idleTime > 10 && + Math.floor(Math.random() * 200) == 0 && + idleAnimation == null + ) { + let avalibleIdleAnimations = ["sleeping", "scratchSelf"]; + if (nekoPosX < 32) { + avalibleIdleAnimations.push("scratchWallW"); + } + if (nekoPosY < 32) { + avalibleIdleAnimations.push("scratchWallN"); + } + if (nekoPosX > window.innerWidth - 32) { + avalibleIdleAnimations.push("scratchWallE"); + } + if (nekoPosY > window.innerHeight - 32) { + avalibleIdleAnimations.push("scratchWallS"); + } + idleAnimation = + avalibleIdleAnimations[ + Math.floor(Math.random() * avalibleIdleAnimations.length) + ]; + } + + switch (idleAnimation) { + case "sleeping": + if (idleAnimationFrame < 8) { + setSprite("tired", 0); + break; + } + setSprite("sleeping", Math.floor(idleAnimationFrame / 4)); + if (idleAnimationFrame > 192) { + resetIdleAnimation(); + } + break; + case "scratchWallN": + case "scratchWallS": + case "scratchWallE": + case "scratchWallW": + case "scratchSelf": + setSprite(idleAnimation, idleAnimationFrame); + if (idleAnimationFrame > 9) { + resetIdleAnimation(); + } + break; + default: + setSprite("idle", 0); + return; + } + idleAnimationFrame += 1; + } + + function frame() { + frameCount += 1; + const diffX = nekoPosX - mousePosX; + const diffY = nekoPosY - mousePosY; + const distance = Math.sqrt(diffX ** 2 + diffY ** 2); + + if (distance < nekoSpeed || distance < 48) { + idle(); + return; + } + + idleAnimation = null; + idleAnimationFrame = 0; + + if (idleTime > 1) { + setSprite("alert", 0); + // count down after being alerted before moving + idleTime = Math.min(idleTime, 7); + idleTime -= 1; + return; + } + + let direction; + direction = diffY / distance > 0.5 ? "N" : ""; + direction += diffY / distance < -0.5 ? "S" : ""; + direction += diffX / distance > 0.5 ? "W" : ""; + direction += diffX / distance < -0.5 ? "E" : ""; + setSprite(direction, frameCount); + + nekoPosX -= (diffX / distance) * nekoSpeed; + nekoPosY -= (diffY / distance) * nekoSpeed; + + nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16); + nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16); + + nekoEl.style.left = `${nekoPosX - 16}px`; + nekoEl.style.top = `${nekoPosY - 16}px`; + } + + init(); +} diff --git a/src/js/script.js b/src/js/script.js new file mode 100644 index 0000000..8ba3d82 --- /dev/null +++ b/src/js/script.js @@ -0,0 +1,87 @@ +import Prism from 'prismjs'; +import 'prismjs/components/prism-javascript'; +import 'prismjs/components/prism-css'; +import 'prismjs/components/prism-markup'; +import { initOneko } from './oneko.js'; + +(() => { + const toggleButton = document.getElementById("theme-toggle"); + const html = document.documentElement; + + const sessionTheme = sessionStorage.getItem("theme"); + const systemPrefersDark = window.matchMedia( + "(prefers-color-scheme: dark)", + ).matches; + + const initialTheme = sessionTheme || (systemPrefersDark ? "dark" : "light"); + + if (initialTheme === "dark") { + html.setAttribute("data-theme", "dark"); + toggleButton.checked = true; + } + + toggleButton.addEventListener("change", () => { + const theme = html.getAttribute("data-theme"); + + if (theme === "dark") { + html.removeAttribute("data-theme"); + sessionStorage.setItem("theme", "light"); + toggleButton.checked = false; + } else { + html.setAttribute("data-theme", "dark"); + sessionStorage.setItem("theme", "dark"); + toggleButton.checked = true; + } + }); +})(); + +(() => { + const colors = [ + "#ff69b4", + "#b19cd9", + "#8b6f47", + "#ff85c0", + "#c4b5fd", + "#d4a574", + ]; + const shapes = ["❀", "✿", "✽", "✾", "✻", "❊", "❋", "✼"]; + + document.addEventListener("mousemove", (e) => { + createParticle(e.clientX, e.clientY); + }); + + const createParticle = (x, y) => { + const particle = document.createElement("div"); + particle.className = "fairy-dust"; + + const shape = shapes[Math.floor(Math.random() * shapes.length)]; + const size = Math.random() * 8 + 6; + const color = colors[Math.floor(Math.random() * colors.length)]; + const offsetX = (Math.random() - 0.5) * 20; + const offsetY = (Math.random() - 0.5) * 20; + const rotation = Math.random() * 360; + + particle.textContent = shape; + particle.style.cssText = ` + position: fixed; + left: ${x + offsetX}px; + top: ${y + offsetY}px; + font-size: ${size}px; + color: ${color}; + opacity: 0.4; + pointer-events: none; + z-index: 9001; /* it's over 9000 */ + line-height: 1; + transform: rotate(${rotation}deg); + animation: fairy-float 0.8s ease-out forwards; + `; + + document.body.appendChild(particle); + setTimeout(() => particle.remove(), 800); + }; +})(); + +document.addEventListener('DOMContentLoaded', () => { + Prism.highlightAll(); + initOneko(); +}); -- cgit v1.2.3-70-g09d2