diff --git a/Config.qml b/Config.qml new file mode 100644 index 0000000..9de1709 --- /dev/null +++ b/Config.qml @@ -0,0 +1,147 @@ +import QtQuick +import Quickshell.Io + +QtObject { + id: root + + // Load user config + property FileView configFile: FileView { + path: "./config.json" + JsonAdapter { + id: configAdapter + property string theme: "sleek" + property string pamConfig: "password.conf" + property int timeout: 30 + property bool showDate: true + property bool showHint: true + property bool clock24h: true + } + } + + // Resolve theme path from name. Falls back to sleek if the name is empty or unsafe. + property string resolvedThemePath: { + const name = configAdapter.theme; + if (!name || name === "") return "themes/sleek/sleek.theme.json"; + const safeName = name.replace(/[^a-zA-Z0-9_-]/g, ""); + if (safeName === "") return "themes/sleek/sleek.theme.json"; + return "themes/" + safeName + "/" + safeName + ".theme.json"; + } + + // Single async theme load. Properties start with sleek defaults + // and update when theme.json arrives. No blocking, no watching. + property FileView themeFile: FileView { + id: themeFile + path: root.resolvedThemePath + + JsonAdapter { + id: adapter + + // Base palette + property string bg: "#000000" + property string fg: "#ffffff" + property string error: "#ff4444" + property string selection: "#666666" + + // Font + property string fontFamily: "sans-serif" + + // Type scale + property int fontXs: 9 + property int fontBase: 13 + property int fontLg: 16 + property int fontXl: 24 + property int font3xl: 80 + + // Spacing scale + property int spaceXs: 6 + property int spaceSm: 8 + property int spaceMd: 16 + + // Text colors + property string textPrimary: "#B3FFFFFF" + property string textStatus: "#8CFFFFFF" + property string textSecondary: "#80FFFFFF" + property string textMuted: "#66FFFFFF" + property string textHint: "#59FFFFFF" + property string textDisabled: "#4DFFFFFF" + property string textTertiary: "#26FFFFFF" + + // Surface colors + property string surfaceBg: "#1AFFFFFF" + property string surfaceButton: "#14FFFFFF" + property string surfaceDisabled: "#0DFFFFFF" + property string spinnerStroke: "#99FFFFFF" + + // Background layers + property string wallpaper: "" + property real bgImageOpacity: 0.6 + property real overlayOpacity: 0.3 + + // Component sizing + property int passwordWidth: 200 + property int passwordPadding: 12 + property int passwordRadius: 6 + property int spinnerSize: 24 + property int spinnerMargin: 2 + property int spinnerStrokeWidth: 2 + property int buttonSize: 48 + + // Layout positions + property int clockTopOffset: 60 + property int marginHintBottom: 60 + property int marginCancelBottom: 40 + + // Animation / timers + property int animDuration: 600 + property int timerInterval: 1000 + } + } + + // Behavioural settings (from config.json) + property alias pamConfig: configAdapter.pamConfig + property alias timeout: configAdapter.timeout + property alias showDate: configAdapter.showDate + property alias showHint: configAdapter.showHint + property alias clock24h: configAdapter.clock24h + + // Visual settings (from theme.json, with sleek defaults) + property alias bg: adapter.bg + property alias fg: adapter.fg + property alias error: adapter.error + property alias selection: adapter.selection + property alias fontFamily: adapter.fontFamily + property alias fontXs: adapter.fontXs + property alias fontBase: adapter.fontBase + property alias fontLg: adapter.fontLg + property alias fontXl: adapter.fontXl + property alias font3xl: adapter.font3xl + property alias spaceXs: adapter.spaceXs + property alias spaceSm: adapter.spaceSm + property alias spaceMd: adapter.spaceMd + property alias textPrimary: adapter.textPrimary + property alias textStatus: adapter.textStatus + property alias textSecondary: adapter.textSecondary + property alias textMuted: adapter.textMuted + property alias textHint: adapter.textHint + property alias textDisabled: adapter.textDisabled + property alias textTertiary: adapter.textTertiary + property alias surfaceBg: adapter.surfaceBg + property alias surfaceButton: adapter.surfaceButton + property alias surfaceDisabled: adapter.surfaceDisabled + property alias spinnerStroke: adapter.spinnerStroke + property alias wallpaper: adapter.wallpaper + property alias bgImageOpacity: adapter.bgImageOpacity + property alias overlayOpacity: adapter.overlayOpacity + property alias passwordWidth: adapter.passwordWidth + property alias passwordPadding: adapter.passwordPadding + property alias passwordRadius: adapter.passwordRadius + property alias spinnerSize: adapter.spinnerSize + property alias spinnerMargin: adapter.spinnerMargin + property alias spinnerStrokeWidth: adapter.spinnerStrokeWidth + property alias buttonSize: adapter.buttonSize + property alias clockTopOffset: adapter.clockTopOffset + property alias marginHintBottom: adapter.marginHintBottom + property alias marginCancelBottom: adapter.marginCancelBottom + property alias animDuration: adapter.animDuration + property alias timerInterval: adapter.timerInterval +} diff --git a/LockContext.qml b/LockContext.qml index a5f6b33..5a34729 100644 --- a/LockContext.qml +++ b/LockContext.qml @@ -7,6 +7,8 @@ Scope { signal unlocked() signal failed() + Config { id: appConfig } + property string currentText: "" property bool unlockInProgress: false property bool showFailure: false @@ -64,7 +66,7 @@ Scope { Timer { id: timeoutTimer - interval: 30000 + interval: appConfig.timeout * 1000 repeat: false onTriggered: cancelPam() } @@ -72,8 +74,13 @@ Scope { PamContext { id: pam - configDirectory: "pam" - config: "password.conf" + // Support both relative (to pam/) and absolute paths for the PAM config. + property string rawConfig: appConfig.pamConfig || "password.conf" + property bool rawIsAbsolute: rawConfig.charAt(0) === '/' + property int rawLastSlash: rawIsAbsolute ? rawConfig.lastIndexOf('/') : -1 + + configDirectory: rawIsAbsolute ? rawConfig.substring(0, rawLastSlash) : "pam" + config: rawIsAbsolute ? rawConfig.substring(rawLastSlash + 1) : rawConfig onPamMessage: { if (this.responseRequired) { diff --git a/LockSurface.qml b/LockSurface.qml index e1bad32..c95fd90 100644 --- a/LockSurface.qml +++ b/LockSurface.qml @@ -7,26 +7,21 @@ Rectangle { id: root required property LockContext context - readonly property color bg: "#000000" - readonly property color fg: "#ffffff" - readonly property color surface: "#1a1a1a" - readonly property color border: "#333333" - readonly property color accent: "#4a4a4a" - readonly property color error: "#ff4444" + Config { id: config } - color: bg + color: config.bg Image { anchors.fill: parent - source: "background.jpg" + source: config.wallpaper fillMode: Image.PreserveAspectCrop - opacity: 0.6 + opacity: config.bgImageOpacity } Rectangle { anchors.fill: parent - color: "#000000" - opacity: 0.3 + color: config.bg + opacity: config.overlayOpacity } focus: true @@ -52,30 +47,32 @@ Rectangle { Column { id: clockGroup anchors.horizontalCenter: parent.horizontalCenter - y: root.context.pamActivated ? 60 : parent.height / 2 - height / 2 - spacing: 8 + y: root.context.pamActivated ? config.clockTopOffset : parent.height / 2 - height / 2 + spacing: config.spaceSm Behavior on y { NumberAnimation { - duration: 500 + duration: config.animDuration easing.type: Easing.InOutQuad } } Label { id: dateLabel + visible: config.showDate property var date: new Date() anchors.horizontalCenter: parent.horizontalCenter renderType: Text.NativeRendering - font.pointSize: 24 - color: Qt.rgba(1, 1, 1, 0.7) + font.pointSize: config.fontXl + font.family: config.fontFamily + color: config.textPrimary Timer { running: true repeat: true - interval: 1000 + interval: config.timerInterval onTriggered: dateLabel.date = new Date(); } @@ -94,21 +91,28 @@ Rectangle { anchors.horizontalCenter: parent.horizontalCenter renderType: Text.NativeRendering - font.pointSize: 80 - color: Qt.rgba(1, 1, 1, 0.7) + font.pointSize: config.font3xl + font.family: config.fontFamily + color: config.textPrimary Timer { running: true repeat: true - interval: 1000 + interval: config.timerInterval onTriggered: clock.date = new Date(); } text: { - const hours = this.date.getHours().toString().padStart(2, '0'); + let hours = this.date.getHours(); const minutes = this.date.getMinutes().toString().padStart(2, '0'); - return `${hours}:${minutes}`; + if (!config.clock24h) { + const ampm = hours >= 12 ? 'PM' : 'AM'; + hours = hours % 12; + hours = hours ? hours : 12; + return `${hours.toString().padStart(2, '0')}:${minutes} ${ampm}`; + } + return `${hours.toString().padStart(2, '0')}:${minutes}`; } } } @@ -119,19 +123,20 @@ Rectangle { top: parent.verticalCenter } - spacing: 16 + spacing: config.spaceMd // Fingerprint indicator - shown while PAM is asking for a fingerprint Column { visible: root.context.isFingerprintPrompt - spacing: 8 + spacing: config.spaceSm Layout.alignment: Qt.AlignHCenter Label { id: fingerprintIndicator text: "󰈷" - color: fg - font.pointSize: 25 + color: config.fg + font.pointSize: config.fontXl + font.family: config.fontFamily anchors.horizontalCenter: parent.horizontalCenter SequentialAnimation { @@ -143,21 +148,22 @@ Rectangle { target: fingerprintIndicator property: "opacity" to: 0.4 - duration: 2000 + duration: config.animDuration * 2 } NumberAnimation { target: fingerprintIndicator property: "opacity" to: 1.0 - duration: 2000 + duration: config.animDuration * 2 } } } Label { text: root.context.pamMessage !== "" ? root.context.pamMessage : "Place your finger on the sensor" - color: Qt.rgba(1, 1, 1, 0.5) - font.pointSize: 12 + color: config.textSecondary + font.pointSize: config.fontBase + font.family: config.fontFamily anchors.horizontalCenter: parent.horizontalCenter } } @@ -171,8 +177,8 @@ Rectangle { id: passwordBox placeholderText: "Enter password" - implicitWidth: 200 - padding: 12 + implicitWidth: config.passwordWidth + padding: config.passwordPadding focus: true @@ -186,14 +192,14 @@ Rectangle { echoMode: TextInput.Password inputMethodHints: Qt.ImhSensitiveData - color: enabled ? Qt.rgba(1, 1, 1, 0.7) : Qt.rgba(1, 1, 1, 0.3) - selectionColor: "#666666" - selectedTextColor: bg - placeholderTextColor: enabled ? Qt.rgba(1, 1, 1, 0.4) : Qt.rgba(1, 1, 1, 0.15) + color: enabled ? config.textPrimary : config.textDisabled + selectionColor: config.selection + selectedTextColor: config.bg + placeholderTextColor: enabled ? config.textMuted : config.textTertiary background: Rectangle { - color: enabled ? Qt.rgba(1, 1, 1, 0.1) : Qt.rgba(1, 1, 1, 0.05) - radius: 6 + color: enabled ? config.surfaceBg : config.surfaceDisabled + radius: config.passwordRadius } onTextChanged: root.context.currentText = this.text; @@ -225,72 +231,43 @@ Rectangle { } } - // Button { - // id: unlockButton - // implicitWidth: passwordBox.implicitHeight * .8 - // implicitHeight: passwordBox.implicitHeight * .8 - // - // focusPolicy: Qt.NoFocus - // - // enabled: !root.context.unlockInProgress && root.context.currentText !== ""; - // - // contentItem: Text { - // text: "›" - // color: parent.enabled ? Qt.rgba(1, 1, 1, 0.7) : Qt.rgba(1, 1, 1, 0.3) - // font.pointSize: 28 - // horizontalAlignment: Text.AlignHCenter - // verticalAlignment: Text.AlignVCenter - // } - // - // background: Rectangle { - // color: parent.enabled ? Qt.rgba(1, 1, 1, 0.1) : Qt.rgba(1, 1, 1, 0.05) - // radius: width / 2 - // } - // - // onClicked: { - // if (root.context.pamResponseRequired) { - // root.context.respondToPam(passwordBox.text); - // } else { - // Qt.callLater(root.context.tryUnlock); - // } - // } - // } + } // Spinner loader - shown while verifying Item { visible: root.context.unlockInProgress && root.context.pamResponseRequired - implicitWidth: 24 - implicitHeight: 24 + implicitWidth: config.spinnerSize + implicitHeight: config.spinnerSize Layout.alignment: Qt.AlignHCenter Rectangle { anchors.centerIn: parent - width: 20 - height: 20 + width: config.spinnerSize - config.spinnerMargin * 2 + height: config.spinnerSize - config.spinnerMargin * 2 color: "transparent" transform: Rotation { - origin.x: 10 - origin.y: 10 + origin.x: (config.spinnerSize - config.spinnerMargin * 2) / 2 + origin.y: (config.spinnerSize - config.spinnerMargin * 2) / 2 angle: 0 NumberAnimation on angle { from: 0 to: 360 - duration: 800 + duration: config.animDuration * 2 loops: Animation.Infinite } } Canvas { anchors.fill: parent - anchors.margins: 2 + anchors.margins: config.spinnerMargin onPaint: { var ctx = getContext("2d"); ctx.reset(); - ctx.lineWidth = 2; - ctx.strokeStyle = Qt.rgba(1, 1, 1, 0.6); + ctx.lineWidth = config.spinnerStrokeWidth; + ctx.strokeStyle = config.spinnerStroke; ctx.lineCap = "round"; ctx.beginPath(); ctx.arc(width / 2, height / 2, width / 2 - 1, 0, Math.PI * 1.5); @@ -303,24 +280,25 @@ Rectangle { // Retry button - shown after failed auth Column { visible: root.context.pamActivated && root.context.showFailure && !root.context.unlockInProgress - spacing: 6 + spacing: config.spaceXs Layout.alignment: Qt.AlignHCenter Button { - implicitWidth: 48 - implicitHeight: 48 + implicitWidth: config.buttonSize + implicitHeight: config.buttonSize contentItem: Text { text: "↻" - color: Qt.rgba(1, 1, 1, 0.5) - font.pointSize: 16 + color: config.textSecondary + font.pointSize: config.fontLg + font.family: config.fontFamily font.weight: Font.Bold horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } background: Rectangle { - color: Qt.rgba(1, 1, 1, 0.08) + color: config.surfaceButton radius: width / 2 } @@ -329,8 +307,9 @@ Rectangle { Text { text: "Retry" - color: Qt.rgba(1, 1, 1, 0.35) - font.pointSize: 9 + color: config.textHint + font.pointSize: config.fontXs + font.family: config.fontFamily anchors.horizontalCenter: parent.horizontalCenter } } @@ -346,8 +325,9 @@ Rectangle { return "Authentication failed"; return ""; } - color: root.context.pamMessageIsError ? error : Qt.rgba(1, 1, 1, 0.55) - font.pointSize: 13 + color: root.context.pamMessageIsError ? config.error : config.textStatus + font.pointSize: config.fontBase + font.family: config.fontFamily Layout.alignment: Qt.AlignHCenter } } @@ -355,15 +335,16 @@ Rectangle { // Hint label - shown before any auth attempt Label { id: hintLabel - visible: !root.context.pamActivated + visible: config.showHint && !root.context.pamActivated anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom - bottomMargin: 60 + bottomMargin: config.marginHintBottom } text: "Click or press any key to unlock" - color: Qt.rgba(1, 1, 1, 0.4) - font.pointSize: 14 + color: config.textMuted + font.pointSize: config.fontBase + font.family: config.fontFamily SequentialAnimation { id: hintPulseAnimation @@ -374,13 +355,13 @@ Rectangle { target: hintLabel property: "opacity" to: 0.5 - duration: 2000 + duration: config.animDuration * 2 } NumberAnimation { target: hintLabel property: "opacity" to: 1.0 - duration: 2000 + duration: config.animDuration * 2 } } } @@ -390,25 +371,26 @@ Rectangle { anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom - bottomMargin: 40 + bottomMargin: config.marginCancelBottom } - spacing: 6 + spacing: config.spaceXs Button { - implicitWidth: 48 - implicitHeight: 48 + implicitWidth: config.buttonSize + implicitHeight: config.buttonSize contentItem: Text { text: "✕" - color: Qt.rgba(1, 1, 1, 0.5) - font.pointSize: 16 + color: config.textSecondary + font.pointSize: config.fontLg + font.family: config.fontFamily font.weight: Font.Bold horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } background: Rectangle { - color: Qt.rgba(1, 1, 1, 0.08) + color: config.surfaceButton radius: width / 2 } @@ -417,8 +399,9 @@ Rectangle { Text { text: "Cancel" - color: Qt.rgba(1, 1, 1, 0.35) - font.pointSize: 9 + color: config.textHint + font.pointSize: config.fontXs + font.family: config.fontFamily anchors.horizontalCenter: parent.horizontalCenter } } diff --git a/README.md b/README.md index 26b03b2..66c7e3a 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,62 @@ Consider this alpha software. There may be bugs, and it has not been thoroughly PAM config lives in the `pam/` directory. The default config uses `password.conf`. Adjust to your system as needed. -Replace `background.jpg` with your own wallpaper if you like. +## Themes + +Themes live in the `themes/` directory. Each theme has its own folder containing a `.theme.json` file and a `wallpaper.jpg`. + +The active theme is selected in `config.json` at the project root: + +```json +{ + "theme": "sleek", + "pamConfig": "password.conf", + "timeout": 30, + "showDate": true, + "showHint": true, + "clock24h": true +} +``` + +All fields are optional and fall back to sensible defaults if omitted. + +| Field | Default | Description | +|---|---|---| +| `theme` | `sleek` | Theme name (must match a folder in `themes/`) | +| `pamConfig` | `password.conf` | PAM configuration file. Relative paths are resolved inside the `pam/` directory; absolute paths (e.g. `/etc/pam.d/system-auth`) are used as-is. | +| `timeout` | `30` | Seconds before cancelling an authentication attempt | +| `showDate` | `true` | Show the date label above the clock | +| `showHint` | `true` | Show the "Click or press any key to unlock" hint | +| `clock24h` | `true` | Use 24-hour clock format (set to `false` for 12-hour) | + +Available themes: +- `sleek` — default dark minimal theme +- `catppuccin` — soft pastel Catppuccin Mocha palette +- `tokyonight` — dark neon Tokyo Night palette +- `nightfox` — deep blue-gray Nightfox palette + +If the configured theme is missing or broken, the lockscreen falls back to the built-in `sleek` defaults. + +### Creating a custom theme + +Copy an existing theme folder, rename it, and adjust the colors in the `.theme.json` file. Only visual tokens need to be defined — layout, sizing, and animation defaults are handled automatically: + +```json +{ + "bg": "#1e1e2e", + "fg": "#cdd6f4", + "error": "#f38ba8", + "fontFamily": "monospace", + "textPrimary": "#f5c2e7", + "textSecondary": "#89b4fa", + "textMuted": "#94e2d5", + "surfaceBg": "#313244", + "spinnerStroke": "#cba6f7", + "wallpaper": "themes/mytheme/wallpaper.jpg" +} +``` + +Advanced users can still override any layout or animation property by adding it to the theme JSON (the keys match the QML properties in `Config.qml`). ## Development diff --git a/config.json b/config.json new file mode 100644 index 0000000..e4800fa --- /dev/null +++ b/config.json @@ -0,0 +1,8 @@ +{ + "theme": "sleek", + "pamConfig": "password.conf", + "timeout": 30, + "showDate": true, + "showHint": true, + "clock24h": true +} diff --git a/themes/catppuccin/README.md b/themes/catppuccin/README.md new file mode 100644 index 0000000..8f9ba1f --- /dev/null +++ b/themes/catppuccin/README.md @@ -0,0 +1,31 @@ +# Catppuccin Theme + +A soft, pastel-colored theme for quicklock based on the [Catppuccin Mocha](https://catppuccin.com/) palette. + +## Colors + +| Property | Color | Usage | +|---|---|---| +| `bg` | `#1e1e2e` | Background | +| `textPrimary` | `#f5c2e7` | Clock, date, password | +| `textStatus` | `#fab387` | Status messages | +| `textSecondary` | `#89b4fa` | Fingerprint subtitle | +| `textMuted` | `#94e2d5` | Hint text | +| `textHint` | `#a6e3a1` | Labels | +| `error` | `#f38ba8` | Auth failure | +| `spinnerStroke` | `#cba6f7` | Loading spinner | + +## Wallpaper + +![Catppuccin wallpaper](preview.jpg) + +## Usage + +Edit `config.json`: + +```json +{ + "theme": "catppuccin" +} +``` + diff --git a/themes/catppuccin/catppuccin.theme.json b/themes/catppuccin/catppuccin.theme.json new file mode 100644 index 0000000..529686c --- /dev/null +++ b/themes/catppuccin/catppuccin.theme.json @@ -0,0 +1,20 @@ +{ + "_comment": "Catppuccin Mocha theme for quicklock", + "bg": "#1e1e2e", + "fg": "#cdd6f4", + "error": "#f38ba8", + "selection": "#cba6f7", + "fontFamily": "monospace", + "textPrimary": "#f5c2e7", + "textStatus": "#fab387", + "textSecondary": "#89b4fa", + "textMuted": "#94e2d5", + "textHint": "#a6e3a1", + "textDisabled": "#6c7086", + "textTertiary": "#45475a", + "surfaceBg": "#313244", + "surfaceButton": "#45475a", + "surfaceDisabled": "#313244", + "spinnerStroke": "#cba6f7", + "wallpaper": "themes/catppuccin/wallpaper.jpg" +} diff --git a/themes/catppuccin/preview.jpg b/themes/catppuccin/preview.jpg new file mode 100644 index 0000000..0c51069 Binary files /dev/null and b/themes/catppuccin/preview.jpg differ diff --git a/themes/catppuccin/wallpaper.jpg b/themes/catppuccin/wallpaper.jpg new file mode 100644 index 0000000..820771b Binary files /dev/null and b/themes/catppuccin/wallpaper.jpg differ diff --git a/themes/nightfox/README.md b/themes/nightfox/README.md new file mode 100644 index 0000000..dd10776 --- /dev/null +++ b/themes/nightfox/README.md @@ -0,0 +1,31 @@ +# Nightfox Theme + +A deep blue-gray theme for quicklock inspired by the [Nightfox](https://github.com/EdenEast/nightfox.nvim) colorscheme. + +## Colors + +| Property | Color | Usage | +|---|---|---| +| `bg` | `#192330` | Background | +| `textPrimary` | `#e4cfff` | Clock, date, password | +| `textStatus` | `#f4a261` | Status messages | +| `textSecondary` | `#63cdcf` | Fingerprint subtitle | +| `textMuted` | `#81b29a` | Hint text | +| `textHint` | `#d67ad2` | Labels | +| `error` | `#c94f6d` | Auth failure | +| `spinnerStroke` | `#d67ad2` | Loading spinner | + +## Wallpaper + +![Nightfox wallpaper](preview.jpg) + +## Usage + +Edit `config.json`: + +```json +{ + "theme": "nightfox" +} +``` + diff --git a/themes/nightfox/nightfox.theme.json b/themes/nightfox/nightfox.theme.json new file mode 100644 index 0000000..b0a781b --- /dev/null +++ b/themes/nightfox/nightfox.theme.json @@ -0,0 +1,20 @@ +{ + "_comment": "Nightfox theme for quicklock", + "bg": "#192330", + "fg": "#cdcecf", + "error": "#c94f6d", + "selection": "#719cd6", + "fontFamily": "monospace", + "textPrimary": "#e4cfff", + "textStatus": "#f4a261", + "textSecondary": "#63cdcf", + "textMuted": "#81b29a", + "textHint": "#d67ad2", + "textDisabled": "#738091", + "textTertiary": "#39506d", + "surfaceBg": "#212e3f", + "surfaceButton": "#39506d", + "surfaceDisabled": "#212e3f", + "spinnerStroke": "#d67ad2", + "wallpaper": "themes/nightfox/wallpaper.jpg" +} diff --git a/themes/nightfox/preview.jpg b/themes/nightfox/preview.jpg new file mode 100644 index 0000000..762be93 Binary files /dev/null and b/themes/nightfox/preview.jpg differ diff --git a/themes/nightfox/wallpaper.jpg b/themes/nightfox/wallpaper.jpg new file mode 100644 index 0000000..4b971ce Binary files /dev/null and b/themes/nightfox/wallpaper.jpg differ diff --git a/themes/sleek/README.md b/themes/sleek/README.md new file mode 100644 index 0000000..7c97f1f --- /dev/null +++ b/themes/sleek/README.md @@ -0,0 +1,30 @@ +# Sleek Theme + +The default dark theme for quicklock. Clean, minimal, and monochrome with subtle transparency. + +## Colors + +| Property | Color | Usage | +|---|---|---| +| `bg` | `#000000` | Background | +| `textPrimary` | `#B3FFFFFF` | Clock, date, password | +| `textStatus` | `#8CFFFFFF` | Status messages | +| `textSecondary` | `#80FFFFFF` | Fingerprint subtitle | +| `textMuted` | `#66FFFFFF` | Hint text | +| `textHint` | `#59FFFFFF` | Labels | +| `error` | `#ff4444` | Auth failure | +| `spinnerStroke` | `#99FFFFFF` | Loading spinner | + +## Wallpaper + +![Sleek wallpaper](preview.jpg) + +## Usage + +This is the default theme. To switch back to it, edit `config.json`: + +```json +{ + "theme": "sleek" +} +``` diff --git a/themes/sleek/preview.jpg b/themes/sleek/preview.jpg new file mode 100644 index 0000000..3843927 Binary files /dev/null and b/themes/sleek/preview.jpg differ diff --git a/themes/sleek/sleek.theme.json b/themes/sleek/sleek.theme.json new file mode 100644 index 0000000..e14d5f6 --- /dev/null +++ b/themes/sleek/sleek.theme.json @@ -0,0 +1,20 @@ +{ + "_comment": "Sleek default theme for quicklock", + "bg": "#000000", + "fg": "#ffffff", + "error": "#ff4444", + "selection": "#666666", + "fontFamily": "sans-serif", + "textPrimary": "#B3FFFFFF", + "textStatus": "#8CFFFFFF", + "textSecondary": "#80FFFFFF", + "textMuted": "#66FFFFFF", + "textHint": "#59FFFFFF", + "textDisabled": "#4DFFFFFF", + "textTertiary": "#26FFFFFF", + "surfaceBg": "#1AFFFFFF", + "surfaceButton": "#14FFFFFF", + "surfaceDisabled": "#0DFFFFFF", + "spinnerStroke": "#99FFFFFF", + "wallpaper": "themes/sleek/wallpaper.jpg" +} diff --git a/background.jpg b/themes/sleek/wallpaper.jpg similarity index 100% rename from background.jpg rename to themes/sleek/wallpaper.jpg diff --git a/themes/tokyonight/README.md b/themes/tokyonight/README.md new file mode 100644 index 0000000..a30bb35 --- /dev/null +++ b/themes/tokyonight/README.md @@ -0,0 +1,31 @@ +# Tokyo Night Theme + +A dark, neon-accented theme for quicklock based on the [Tokyo Night](https://github.com/folke/tokyonight.nvim) palette. + +## Colors + +| Property | Color | Usage | +|---|---|---| +| `bg` | `#1a1b26` | Background | +| `textPrimary` | `#c0caf5` | Clock, date, password | +| `textStatus` | `#e0af68` | Status messages | +| `textSecondary` | `#7aa2f7` | Fingerprint subtitle | +| `textMuted` | `#9ece6a` | Hint text | +| `textHint` | `#bb9af7` | Labels | +| `error` | `#f7768e` | Auth failure | +| `spinnerStroke` | `#7dcfff` | Loading spinner | + +## Wallpaper + +![Tokyo Night wallpaper](preview.jpg) + +## Usage + +Edit `config.json`: + +```json +{ + "theme": "tokyonight" +} +``` + diff --git a/themes/tokyonight/preview.jpg b/themes/tokyonight/preview.jpg new file mode 100644 index 0000000..0bdf670 Binary files /dev/null and b/themes/tokyonight/preview.jpg differ diff --git a/themes/tokyonight/tokyonight.theme.json b/themes/tokyonight/tokyonight.theme.json new file mode 100644 index 0000000..0434da3 --- /dev/null +++ b/themes/tokyonight/tokyonight.theme.json @@ -0,0 +1,20 @@ +{ + "_comment": "Tokyo Night theme for quicklock", + "bg": "#1a1b26", + "fg": "#a9b1d6", + "error": "#f7768e", + "selection": "#7aa2f7", + "fontFamily": "monospace", + "textPrimary": "#c0caf5", + "textStatus": "#e0af68", + "textSecondary": "#7aa2f7", + "textMuted": "#9ece6a", + "textHint": "#bb9af7", + "textDisabled": "#565f89", + "textTertiary": "#414868", + "surfaceBg": "#24283b", + "surfaceButton": "#414868", + "surfaceDisabled": "#24283b", + "spinnerStroke": "#7dcfff", + "wallpaper": "themes/tokyonight/wallpaper.jpg" +} diff --git a/themes/tokyonight/wallpaper.jpg b/themes/tokyonight/wallpaper.jpg new file mode 100644 index 0000000..8b4eb3a Binary files /dev/null and b/themes/tokyonight/wallpaper.jpg differ