diff --git a/app/src/App.css b/app/src/App.css new file mode 100644 index 0000000..5804fcd --- /dev/null +++ b/app/src/App.css @@ -0,0 +1,87 @@ +@font-face { + font-family: "CascadiaCode"; + src: url("./assets/fonts/CaskaydiaCoveNerdFont-Regular.ttf") + format("truetype"); + font-weight: normal; + font-style: normal; +} +@font-face { + font-family: "CascadiaCode"; + src: url("./assets/fonts/CaskaydiaCoveNerdFont-Bold.ttf") format("truetype"); + font-weight: bold; + font-style: normal; +} + +:root { + font-family: "CascadiaCode", monospace; + font-size: 16px; + overflow: hidden; + + --color-bg-base: #1c2128; + --color-bg-elevated: #22272e; + + --color-border-subtle: #2d333b; + --color-border-muted: #444c56; + --color-border-deep: #111; + + --color-text-primary: #adbac7; + --color-text-secondary: #768390; + --color-text-emphasis: #cdd9e5; + + --color-accent: #b39cd0; + --color-accent-hover: rgba(179, 156, 208, 0.85); + --color-accent-text-on: #1c2128; + + --color-success: #4ade80; + --color-error: #d47373; + --color-error-bg: rgba(212, 115, 115, 0.05); + --color-error-bg-strong: rgba(212, 115, 115, 0.1); + --color-error-border: rgba(212, 115, 115, 0.15); + + --color-shadow-key: rgba(0, 0, 0, 0.3); + --color-spinner-track: rgba(28, 33, 40, 0.2); + + color: var(--color-text-primary); + background-color: var(--color-bg-base); +} + +body { + margin: 0; + padding: 0; + height: 100vh; + width: 100vw; + display: flex; + justify-content: center; + align-items: flex-start; + background-color: var(--color-bg-base); +} + +.app-container { + width: 28.125rem; + height: 37.5rem; + box-sizing: border-box; + padding: 1.5rem; + display: flex; + flex-direction: column; + background: var(--color-bg-base); + border: 1px solid var(--color-border-subtle); + overflow-y: auto; + overflow-x: hidden; +} + +.app-container::-webkit-scrollbar { + width: 0.375rem; +} + +.app-container::-webkit-scrollbar-track { + background: transparent; +} + +.app-container::-webkit-scrollbar-thumb { + background: var(--color-border-subtle); + border-radius: 0.1875rem; +} + +.app-container::-webkit-scrollbar-thumb:hover { + background: var(--color-border-muted); +} diff --git a/app/src/App.module.css b/app/src/App.module.css new file mode 100644 index 0000000..7cb1db0 --- /dev/null +++ b/app/src/App.module.css @@ -0,0 +1,34 @@ +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.25rem; + padding-bottom: 0.75rem; + border-bottom: 1px solid var(--color-bg-elevated); +} + +.logo-section { + display: flex; + align-items: center; + gap: 0.625rem; +} + +.logo-icon { + width: 1.5rem; + height: 1.5rem; + background: var(--color-accent); + border-radius: 0.25rem; + display: flex; + justify-content: center; + align-items: center; + color: var(--color-accent-text-on); + font-weight: 700; + font-size: 1rem; +} + +.logo-title { + font-size: 1.15rem; + font-weight: bold; + letter-spacing: 0.2px; + color: var(--color-text-emphasis); +} diff --git a/app/src/assets/fonts/CaskaydiaCoveNerdFont-Bold.ttf b/app/src/assets/fonts/CaskaydiaCoveNerdFont-Bold.ttf new file mode 100644 index 0000000..d7ec691 Binary files /dev/null and b/app/src/assets/fonts/CaskaydiaCoveNerdFont-Bold.ttf differ diff --git a/app/src/assets/fonts/CaskaydiaCoveNerdFont-Regular.ttf b/app/src/assets/fonts/CaskaydiaCoveNerdFont-Regular.ttf new file mode 100644 index 0000000..b1d98df Binary files /dev/null and b/app/src/assets/fonts/CaskaydiaCoveNerdFont-Regular.ttf differ diff --git a/app/src/components/GeneralTab.module.css b/app/src/components/GeneralTab.module.css new file mode 100644 index 0000000..bd5e3df --- /dev/null +++ b/app/src/components/GeneralTab.module.css @@ -0,0 +1,73 @@ +.hotkey-display-group { + display: flex; + align-items: center; + cursor: pointer; + padding: 0.5rem; + border-radius: 0.25rem; + background: var(--color-bg-base); + border: 1px solid var(--color-border-subtle); + transition: all 0.2s; +} + +.hotkey-display-group:hover { + border-color: var(--color-accent); + background: var(--color-bg-elevated); +} + +.hotkey-display-group.disabled { + opacity: 0.4; + cursor: not-allowed; + background-color: var(--color-bg-base); +} + +.hotkey-display-group.disabled:hover { + border-color: var(--color-border-subtle); + background: var(--color-bg-base); +} + +.hotkey-display-group.recording { + background: var(--color-error-bg-strong); + border-color: var(--color-error); + color: var(--color-error); + animation: pulse-red 1.5s infinite; +} + +.key-cap { + background-color: var(--color-bg-elevated); + border: 1px solid var(--color-border-muted); + border-bottom: 0.125rem solid var(--color-border-deep); + border-radius: 0.1875rem; + padding: 0.125rem 0.375rem; + font-family: "CascadiaCode", monospace; + font-size: 0.75rem; + color: var(--color-text-primary); + box-shadow: 0 1px 0 var(--color-shadow-key); +} + +.key-plus { + color: var(--color-text-secondary); + margin: 0 0.25rem; + font-weight: bold; +} + +.conflict-warning { + margin-top: 0.5rem; + font-size: 0.75rem; + color: var(--color-error); + background: var(--color-error-bg); + border: 1px solid var(--color-error-border); + padding: 0.375rem 0.625rem; + border-radius: 0.25rem; +} + +@keyframes pulse-red { + 0% { + opacity: 0.8; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.8; + } +} diff --git a/app/src/components/LicenseTab.module.css b/app/src/components/LicenseTab.module.css new file mode 100644 index 0000000..a2fc87b --- /dev/null +++ b/app/src/components/LicenseTab.module.css @@ -0,0 +1,129 @@ +.license-active { + color: var(--color-success); +} + +.license-unregistered { + color: var(--color-error); +} + +.license-activation-area { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding-bottom: 1rem; +} + +.license-input-group { + display: flex; + gap: 0.5rem; +} + +.license-input { + flex-grow: 1; + background: var(--color-bg-base); + border: 1px solid var(--color-border-subtle); + color: var(--color-text-emphasis); + padding: 0.375rem 0.625rem; + border-radius: 0.25rem; + font-family: inherit; + font-size: 0.8rem; + outline: none; +} + +.license-input:focus { + border-color: var(--color-accent); +} + +.license-input:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.activate-btn { + background: var(--color-accent); + border: none; + color: var(--color-accent-text-on); + padding: 0.375rem 0.75rem; + border-radius: 0.25rem; + cursor: pointer; + font-weight: bold; + font-family: inherit; + transition: background 0.15s; +} + +.activate-btn:hover { + background: var(--color-accent-hover); +} + +.activate-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.error-text { + font-size: 0.75rem; + color: var(--color-error); + margin-top: 0.25rem; +} + +.hwid-container { + display: flex; + flex-direction: column; + gap: 0.25rem; + background: var(--color-bg-base); + padding: 0.625rem; + border-radius: 0.25rem; + border: 1px solid var(--color-border-subtle); + margin: 0.5rem 0; +} + +.hwid-label { + font-size: 0.7rem; + color: var(--color-text-secondary); + text-transform: uppercase; +} + +.hwid-val { + font-family: "CascadiaCode", monospace; + font-size: 0.75rem; + color: var(--color-text-primary); + word-break: break-all; +} + +.spinner-container { + display: flex; + align-items: center; + gap: 0.375rem; + justify-content: center; +} + +.spinner { + width: 0.875rem; + height: 0.875rem; + border: 0.125rem solid var(--color-spinner-track); + border-top-color: var(--color-accent-text-on); + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.offline-toggle { + background: none; + border: none; + color: var(--color-text-secondary); + font-size: 0.75rem; + cursor: pointer; + padding: 0; + text-align: left; + font-family: inherit; +} + +.offline-toggle:hover { + color: var(--color-accent); + text-decoration: underline; +} diff --git a/app/src/components/RulesTab.module.css b/app/src/components/RulesTab.module.css new file mode 100644 index 0000000..4cc56f6 --- /dev/null +++ b/app/src/components/RulesTab.module.css @@ -0,0 +1,50 @@ +.input-row { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: 0.5rem; +} + +.custom-params-input { + width: 20rem; + padding: 0.375rem 0.625rem; + font-size: 0.8rem; + background: var(--color-bg-base); + border: 1px solid var(--color-border-muted); + border-radius: 0.25rem; + color: var(--color-text-primary); +} + +.custom-params-input:focus { + outline: none; + border-color: var(--color-accent); +} + +.custom-params-input::placeholder { + color: var(--color-text-secondary); + opacity: 0.6; +} + +.save-indicator { + font-size: 0.75rem; + white-space: nowrap; +} + +.save-indicator--debouncing { + color: var(--color-text-secondary); +} + +.save-indicator--saved { + color: var(--color-accent); + animation: fade-out 1.5s ease forwards; +} + +@keyframes fade-out { + 0%, + 70% { + opacity: 1; + } + 100% { + opacity: 0; + } +} diff --git a/app/src/components/SettingsLayout.module.css b/app/src/components/SettingsLayout.module.css new file mode 100644 index 0000000..58a2ac6 --- /dev/null +++ b/app/src/components/SettingsLayout.module.css @@ -0,0 +1,54 @@ +.section { + display: flex; + flex-direction: column; + gap: 0.75rem; + margin-bottom: 1.25rem; +} + +.section-title { + font-size: 0.75rem; + font-weight: bold; + text-transform: uppercase; + color: var(--color-text-secondary); + letter-spacing: 1.5px; +} + +.settings-list { + background: var(--color-bg-elevated); + border: 1px solid var(--color-border-subtle); + border-radius: 0.375rem; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.settings-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1rem; + border-bottom: 1px solid var(--color-border-subtle); +} + +.settings-row:last-child { + border-bottom: none; +} + +.settings-info { + display: flex; + flex-direction: column; + gap: 0.125rem; + padding-right: 0.75rem; +} + +.settings-name { + font-weight: 500; + font-size: 0.9rem; + color: var(--color-text-primary); +} + +.settings-desc { + font-size: 0.75rem; + color: var(--color-text-secondary); + line-height: 1.3; +} diff --git a/app/src/components/TabHeaders.module.css b/app/src/components/TabHeaders.module.css new file mode 100644 index 0000000..5b45d88 --- /dev/null +++ b/app/src/components/TabHeaders.module.css @@ -0,0 +1,46 @@ +.tab-headers { + display: flex; + gap: 1rem; + border-bottom: 1px solid var(--color-border-subtle); + margin-bottom: 1.25rem; +} + +.tab-btn { + background: transparent; + border: none; + color: var(--color-text-secondary); + padding: 0.375rem 0.25rem; + cursor: pointer; + font-weight: 500; + font-family: inherit; + position: relative; + transition: color 0.15s; +} + +.tab-btn:hover { + color: var(--color-text-primary); +} + +.tab-btn:disabled { + opacity: 0.35; + cursor: not-allowed; +} + +.tab-btn:disabled:hover { + color: var(--color-text-secondary); +} + +.tab-btn.active { + color: var(--color-accent); + font-weight: bold; +} + +.tab-btn.active::after { + content: ""; + position: absolute; + bottom: -1px; + left: 0; + right: 0; + height: 0.125rem; + background-color: var(--color-accent); +} diff --git a/app/src/components/ToggleSwitch.module.css b/app/src/components/ToggleSwitch.module.css new file mode 100644 index 0000000..ca2f6b3 --- /dev/null +++ b/app/src/components/ToggleSwitch.module.css @@ -0,0 +1,62 @@ +.switch { + position: relative; + display: inline-block; + width: 2.25rem; + height: 1.125rem; + flex-shrink: 0; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--color-bg-base); + border: 1px solid var(--color-border-muted); + border-radius: 0.1875rem; + transition: 0.15s; +} + +.slider:before { + position: absolute; + content: ""; + height: 0.75rem; + width: 0.75rem; + left: 0.125rem; + bottom: 0.125rem; + background-color: var(--color-text-secondary); + transition: 0.15s; + border-radius: 1px; +} + +.switch input:checked + .slider { + background-color: var(--color-bg-base); + border-color: var(--color-accent); +} + +.switch input:checked + .slider:before { + transform: translateX(1.125rem); + background-color: var(--color-accent); +} + +.switch input:disabled + .slider { + opacity: 0.35; + cursor: not-allowed; +} + +.switch input:disabled + .slider:before { + cursor: not-allowed; +} + +.switch input:focus-visible + .slider { + outline: 0.125rem solid var(--color-accent); + outline-offset: 0.125rem; +}