theme: calm YoRHa lock screen and power menu; nvim and btop follow

The lock screen and the power menu lose the last Persona motion: no split
display type, no grid sweeping in behind a dark column, no tilted slabs, no
skewed password blocks or hard shake. Both are now one small group of type in
the dark — the porthole mark, a line of light display type, a status line, and
either a rounded password field that fills with dots or a NieR menu whose
selected row is a pill of light (rust for the session-ending three). The lock
keeps the wallpaper almost whole; a wrong password nudges the field once and
turns it to rust. P5Backdrop and the grid texture had no users left and go.

Neovim moves to a local yorha colorscheme (nvim submodule) and btop gets a
yorha theme with hairline boxes and grey-to-light gradients.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nvrlandClaude Opus 5.5 committed 2026-10-04 21:09:07 +02:00
1 parent a8c19961e2
commit 63a910bf14
11 files changed
+581 -565

No files matched your search

+286 -259
View File
@@ -10,6 +10,13 @@ import "root:/lock"
// The lock screen's whole face, kept separate from WlSessionLockSurface so it
// can be instantiated in an ordinary window for development without actually
// locking the session.
//
// Quiet on purpose. The wallpaper stays almost whole — the porthole, the
// planet, the girl looking out — and the face is one small group of type set
// in the dark of the porthole: the mark, the time, the date, a short status
// log, and a rounded field to type into. Nothing sweeps, splits or shakes;
// the group resolves in, and a wrong password nudges it once and turns its
// dots to rust.
Item {
id: surface
@@ -20,12 +27,12 @@ Item {
property string status: ""
property bool failed: false
property bool busy: pam.active
readonly property real uiScale: Math.max(0.72, Math.min(1.15,
readonly property real uiScale: Math.max(0.72, Math.min(1.3,
Math.min(width / 1920, height / 1080)))
Rectangle {
anchors.fill: parent
color: Theme.base
color: Theme.ink
}
// ------------------------------------------------------- background
@@ -43,290 +50,312 @@ Item {
fillMode: Image.PreserveAspectCrop
asynchronous: false
cache: true
opacity: 0
// Barely softened: enough that the scene steps back behind the type,
// not so much that it stops being a place.
layer.enabled: true
layer.effect: MultiEffect {
blurEnabled: true
blur: 0.55
blurMax: 48
saturation: -0.15
brightness: -0.35
blur: 0.2
blurMax: 32
saturation: -0.1
brightness: -0.18
}
}
// Wash pulling the photo down toward the shell's ground. Was described as
// a teal wash back when the UI was teal; the colours it actually uses are
// the neutral surfaces, which is what it should have been all along.
// Shade deepening toward the left, where the type sits in the porthole's
// dark, and a breath of it along the foot.
Rectangle {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: Theme.alpha(Theme.deep, 0.55) }
GradientStop { position: 0.6; color: Theme.alpha(Theme.base, 0.75) }
GradientStop { position: 1.0; color: Theme.alpha(Theme.base, 0.92) }
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: Theme.alpha(Theme.ink, 0.72) }
GradientStop { position: 0.45; color: Theme.alpha(Theme.ink, 0.28) }
GradientStop { position: 1.0; color: Theme.alpha(Theme.ink, 0.18) }
}
}
// The same wedge-and-stripes ground the power menu stands on. The wedge
// sits under the clock, which is where the power menu puts it under the
// title — so the two screens land the accent mass in the same place.
P5Backdrop {
id: backdrop
Rectangle {
anchors.fill: parent
reveal: 0
wedgeSpan: 0.30
wedgeOpacity: 0.30
stripeOpacity: 0.04
gradient: Gradient {
GradientStop { position: 0.6; color: "transparent" }
GradientStop { position: 1.0; color: Theme.alpha(Theme.ink, 0.5) }
}
}
// ------------------------------------------------------------ clock
// ------------------------------------------------------------- face
SystemClock {
id: clock
precision: SystemClock.Minutes
}
// The house mark, the same porthole the bar carries far left. Small, above
// the clock — the lock's hero is the time, not the branding.
Porthole {
id: mark
anchors.left: timeBlock.left
anchors.bottom: timeBlock.top
anchors.bottomMargin: 10
size: 28
opacity: timeBlock.opacity
}
Column {
id: timeBlock
anchors.left: parent.left
anchors.leftMargin: parent.width * 0.1
anchors.top: parent.top
anchors.topMargin: parent.height * 0.18
spacing: -18
id: face
x: parent.width * 0.14
y: parent.height * 0.2
width: 380
scale: surface.uiScale
transformOrigin: Item.TopLeft
opacity: 0
spacing: 0
SplitText {
text: Qt.formatDateTime(clock.date, "HH")
pixelSize: Theme.fsMega * 1.7
split: 5
}
transform: Translate { id: drift }
SplitText {
text: Qt.formatDateTime(clock.date, "mm")
pixelSize: Theme.fsMega * 1.7
color: Theme.primary
split: 5
// The house mark, the same porthole the bar carries far left.
Porthole {
size: 26
}
Text {
text: Qt.formatDateTime(clock.date, "dddd, dd MMMM").toUpperCase()
topPadding: Theme.padM
text: Qt.formatDateTime(clock.date, "HH:mm")
color: Theme.accent
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsMega * 1.35
font.weight: Font.Light
font.letterSpacing: 2
// Scaled with the face, so distance-field glyphs: native hinting
// at this size under a scale transform comes out stepped.
renderType: Text.QtRendering
}
Text {
leftPadding: 4
text: Qt.formatDateTime(clock.date, "dddd · dd MMMM").toUpperCase()
color: Theme.subtext
font.family: Theme.fontMono
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsBody
font.letterSpacing: 6
topPadding: 34
font.weight: Theme.weightDisplay
font.letterSpacing: 5
renderType: Text.NativeRendering
}
}
// ------------------------------------------------------ status rail
Column {
anchors.right: parent.right
anchors.rightMargin: parent.width * 0.1
anchors.top: parent.top
anchors.topMargin: parent.height * 0.2
spacing: Theme.padM
width: Math.min(300, parent.width * 0.28)
scale: surface.uiScale
transformOrigin: Item.TopRight
Item { width: 1; height: Theme.padXL }
RailRow {
// ------------------------------------------------------ status log
Column {
width: parent.width
visible: Battery.available
glyph: Battery.icon
label: Math.round(Battery.percent) + "%"
detail: Battery.timeLabel
tone: Battery.tint
leftPadding: 4
StatusRow {
width: parent.width - 4
visible: Battery.available
label: "Battery"
detail: Math.round(Battery.percent) + "%"
+ (Battery.timeLabel ? " " + Battery.timeLabel : "")
tone: Battery.tint
}
StatusRow {
width: parent.width - 4
label: "Network"
detail: Sys.netUp ? Net.label : "no link"
tone: Sys.netUp ? Theme.accentSoft : Theme.overlay
}
StatusRow {
width: parent.width - 4
visible: Media.hasPlayer
label: Media.playing ? "Playing" : "Paused"
detail: Media.artist ? Media.title + " — " + Media.artist : Media.title
tone: Media.playing ? Theme.accent : Theme.primaryDim
}
StatusRow {
width: parent.width - 4
visible: Notifs.count > 0
label: "Notices"
detail: Notifs.count + (Notifs.count === 1 ? " message" : " messages")
tone: Theme.accentSoft
}
}
RailRow {
Item { width: 1; height: Theme.padXL }
// ------------------------------------------------------ the field
Item {
id: authBlock
width: parent.width
glyph: Net.icon
label: Net.label
detail: Sys.netUp ? Sys.netIp : "no link"
tone: Sys.netUp ? Theme.primary : Theme.muted
}
height: field.height + hint.height + Theme.padS
RailRow {
width: parent.width
visible: Media.hasPlayer
glyph: Media.playing ? "󰝚" : "󰏤"
label: Media.title
detail: Media.artist
tone: Theme.primary
}
transform: Translate { id: nudge }
RailRow {
width: parent.width
visible: Notifs.count > 0
glyph: "󱅫"
label: Notifs.count + " notification" + (Notifs.count === 1 ? "" : "s")
detail: Notifs.history.length > 0 ? Notifs.history[0].summary : ""
tone: Theme.accent
}
}
Rectangle {
id: field
width: parent.width
height: 48
radius: height / 2
color: Theme.alpha(Theme.hull, 0.86)
border.width: 1
border.color: surface.failed
? Theme.alarm
: (input.activeFocus && surface.entry.length > 0 ? Theme.outline : Theme.rim)
// ------------------------------------------------------- auth panel
Item {
id: authBlock
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: parent.height * 0.14
width: Math.min(460, parent.width - Theme.padXL * 2)
height: 150
scale: surface.uiScale
transformOrigin: Item.Bottom
Behavior on border.color {
ColorAnimation { duration: Theme.durBase }
}
transform: Translate { id: shakeShift }
Icon {
id: lockGlyph
anchors.left: parent.left
anchors.leftMargin: Theme.padL
anchors.verticalCenter: parent.verticalCenter
text: "󰌾"
color: surface.failed ? Theme.alarm : Theme.muted
font.pixelSize: Theme.fsLarge
}
SplitText {
id: prompt
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
text: surface.failed
? (surface.status || "WRONG").toUpperCase()
: (surface.busy ? "CHECKING…" : "WHO GOES THERE")
color: surface.failed ? Theme.alarm : Theme.text
pixelSize: Theme.fsTitle
letterSpacing: 4
horizontalAlignment: Text.AlignHCenter
split: surface.failed ? 4 : 1.6
}
Text {
anchors.left: lockGlyph.right
anchors.leftMargin: Theme.padM
anchors.verticalCenter: parent.verticalCenter
visible: surface.entry.length === 0
text: "PASSWORD"
color: Theme.muted
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsSmall
font.letterSpacing: Theme.tracking * 2
renderType: Text.NativeRendering
}
// Password field: one chopped block per character.
P5Panel {
id: field
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: prompt.bottom
anchors.topMargin: Theme.padL
width: Math.min(380, authBlock.width - Theme.padL * 2)
height: 56
fill: Theme.mantle
fillOpacity: 0.94
border: surface.failed
? Theme.accent
: (input.activeFocus ? Theme.primary : Theme.outline)
borderWidth: 2
slash: true
slashColor: surface.failed ? Theme.alarm : Theme.primary
slashWidth: 6
halftone: true
halftoneColor: surface.failed ? Theme.alarm : Theme.primary
halftoneOpacity: 0.08
// One dot per character, each resolving in as it is typed.
// They breathe while PAM is checking.
Row {
id: dots
anchors.left: lockGlyph.right
anchors.leftMargin: Theme.padM
anchors.right: enter.left
anchors.rightMargin: Theme.padM
anchors.verticalCenter: parent.verticalCenter
spacing: 7
clip: true
Row {
anchors.centerIn: parent
spacing: 6
visible: surface.entry.length > 0
Repeater {
model: Math.min(surface.entry.length, 28)
Repeater {
model: Math.min(surface.entry.length, 22)
Rectangle {
id: pip
required property int index
anchors.verticalCenter: parent.verticalCenter
width: 7
height: 7
radius: width / 2
color: surface.failed ? Theme.alarm : Theme.accent
opacity: 0
scale: 0.4
Skew {
required property int index
width: 10
height: 20
color: surface.failed ? Theme.alarm : Theme.primary
Component.onCompleted: arrive.start()
// Each new block snaps in.
Component.onCompleted: pop.start()
NumberAnimation {
id: pop
target: parent
property: "scale"
from: 0.2; to: 1
duration: Theme.durBase
easing.type: Easing.OutCubic
ParallelAnimation {
id: arrive
NumberAnimation {
target: pip; property: "opacity"
to: 1; duration: Theme.durBase; easing.type: Easing.OutCubic
}
NumberAnimation {
target: pip; property: "scale"
to: 1; duration: Theme.durBase; easing.type: Easing.OutCubic
}
}
}
}
SequentialAnimation on opacity {
running: surface.busy
loops: Animation.Infinite
alwaysRunToEnd: true
NumberAnimation { to: 0.35; duration: 520; easing.type: Easing.InOutSine }
NumberAnimation { to: 1; duration: 520; easing.type: Easing.InOutSine }
}
}
// Submit: a disc at the field's end that lights once there is
// something to send.
Rectangle {
id: enter
readonly property bool ready: surface.entry.length > 0 && !surface.busy
anchors.right: parent.right
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
width: 32
height: 32
radius: width / 2
color: enter.ready ? Theme.accent : "transparent"
border.width: enter.ready ? 0 : 1
border.color: Theme.rim
Behavior on color {
ColorAnimation { duration: Theme.durBase }
}
Icon {
anchors.centerIn: parent
text: "󰁔"
color: enter.ready ? Theme.ink : Theme.overlay
font.pixelSize: Theme.fsLarge
}
TapHandler {
onTapped: surface.submit()
}
}
// The real input, invisible but focused.
TextInput {
id: input
anchors.fill: parent
opacity: 0
focus: true
echoMode: TextInput.Password
enabled: !surface.busy
activeFocusOnTab: true
onTextChanged: {
surface.entry = text;
if (surface.failed && text.length > 0) surface.failed = false;
}
onAccepted: surface.submit()
Keys.onEscapePressed: {
input.text = "";
surface.failed = false;
}
}
}
Text {
anchors.centerIn: parent
visible: surface.entry.length === 0 && !surface.busy
text: "PASSWORD"
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsSmall
font.letterSpacing: 5
renderType: Text.NativeRendering
}
// Under the field: who is locked, what is happening, or what went
// wrong — one line, never more.
Row {
id: hint
anchors.top: field.bottom
anchors.topMargin: Theme.padS
anchors.left: parent.left
anchors.leftMargin: Theme.padL
spacing: Theme.padS
// Busy sweep.
Rectangle {
anchors.bottom: parent.bottom
anchors.bottomMargin: 6
width: 60
height: 3
color: Theme.primary
visible: surface.busy
XAnimator on x {
running: surface.busy
loops: Animation.Infinite
from: 20
to: field.width - 80
duration: 700
easing.type: Easing.InOutQuad
}
}
// The real input, invisible but focused.
TextInput {
id: input
anchors.fill: parent
opacity: 0
focus: true
echoMode: TextInput.Password
enabled: !surface.busy
activeFocusOnTab: true
onTextChanged: {
surface.entry = text;
if (surface.failed && text.length === 0) surface.failed = false;
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: 4
height: 4
radius: 2
color: surface.failed ? Theme.alarm : Theme.overlay
}
onAccepted: surface.submit()
Keys.onEscapePressed: {
input.text = "";
surface.failed = false;
Text {
anchors.verticalCenter: parent.verticalCenter
text: surface.failed
? (surface.status || "Access denied").toUpperCase()
: (surface.busy ? "VERIFYING" : (Quickshell.env("USER") || "user"))
color: surface.failed ? Theme.alarm : Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
font.letterSpacing: 2
renderType: Text.NativeRendering
}
}
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: field.bottom
anchors.topMargin: Theme.padM
spacing: Theme.padS
Icon {
anchors.verticalCenter: parent.verticalCenter
text: "󰌾"
color: Theme.muted
font.pixelSize: Theme.fsBody
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: Quickshell.env("USER") || "USER"
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsSmall
font.letterSpacing: 3
renderType: Text.NativeRendering
}
}
}
// Keep focus on the field no matter what.
@@ -362,62 +391,60 @@ Item {
surface.entry = "";
surface.failed = false;
} else {
surface.failed = true;
if (surface.status === "") surface.status = "Access denied";
input.text = "";
surface.entry = "";
shake.restart();
surface.failed = true;
if (surface.status === "") surface.status = "Access denied";
refuse.restart();
}
}
onError: err => {
surface.failed = true;
surface.status = "PAM error";
input.text = "";
surface.entry = "";
shake.restart();
surface.failed = true;
surface.status = "PAM error";
refuse.restart();
}
}
// A wrong password: the field leans away once and comes back, a head
// shaken slowly rather than a buzzer.
SequentialAnimation {
id: shake
loops: 2
id: refuse
NumberAnimation {
target: shakeShift; property: "x"
to: -16; duration: 55; easing.type: Easing.OutQuad
target: nudge; property: "x"
to: -7; duration: 90; easing.type: Easing.OutSine
}
NumberAnimation {
target: shakeShift; property: "x"
to: 16; duration: 55; easing.type: Easing.OutQuad
target: nudge; property: "x"
to: 5; duration: 140; easing.type: Easing.InOutSine
}
NumberAnimation {
target: shakeShift; property: "x"
to: 0; duration: 90; easing.type: Easing.OutCubic
target: nudge; property: "x"
to: 0; duration: 220; easing.type: Easing.OutCubic
}
}
// Entry flourish when the lock comes up.
// The lock comes up the way the scene would if you opened your eyes: the
// picture first, then the type settling a few pixels into place.
Component.onCompleted: intro.start()
ParallelAnimation {
SequentialAnimation {
id: intro
// The stripes sweep in under everything, the same move the power menu
// opens with.
NumberAnimation {
target: backdrop; property: "reveal"
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutExpo
target: wall; property: "opacity"
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutCubic
}
NumberAnimation {
target: timeBlock; property: "opacity"
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutExpo
}
NumberAnimation {
target: authBlock; property: "opacity"
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutExpo
}
NumberAnimation {
target: shakeShift; property: "y"
from: 40; to: 0; duration: Theme.durLazy; easing.type: Easing.OutCubic
ParallelAnimation {
NumberAnimation {
target: face; property: "opacity"
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutCubic
}
NumberAnimation {
target: drift; property: "y"
from: 14; to: 0; duration: Theme.durLazy; easing.type: Easing.OutQuint
}
}
}
}
-63
View File
@@ -1,63 +0,0 @@
import QtQuick
import "root:/config"
import "root:/components"
// One line of the lock screen's status rail.
Item {
id: rail
property string glyph: ""
property string label: ""
property string detail: ""
property color tone: Theme.primary
implicitHeight: visible ? 38 : 0
Rectangle {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: 3
height: 26
color: rail.tone
}
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.padM
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.padS
Icon {
anchors.verticalCenter: parent.verticalCenter
text: rail.glyph
color: rail.tone
font.pixelSize: Theme.fsLarge
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: -2
Text {
text: rail.label
color: Theme.text
elide: Text.ElideRight
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsSmall
font.weight: Font.DemiBold
renderType: Text.NativeRendering
}
Text {
visible: rail.detail !== ""
text: rail.detail
color: Theme.muted
elide: Text.ElideRight
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
renderType: Text.NativeRendering
}
}
}
}
+66
View File
@@ -0,0 +1,66 @@
import QtQuick
import "root:/config"
import "root:/components"
// One line of the lock screen's status log, laid out like the dashboard's
// facts: a dot in the line's tone, the label tracked in caps, a hairline
// running out to the detail at the far end.
Item {
id: row
property string label: ""
property string detail: ""
property color tone: Theme.primaryDim
implicitHeight: visible ? 26 : 0
Rectangle {
id: dot
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: Theme.marker
height: Theme.marker
radius: width / 2
color: row.tone
}
Text {
id: name
anchors.left: dot.right
anchors.leftMargin: Theme.padS
anchors.verticalCenter: parent.verticalCenter
width: Math.min(implicitWidth, row.width * 0.55)
text: row.label.toUpperCase()
color: Theme.text
elide: Text.ElideRight
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsSmall
font.weight: Theme.weightDisplay
font.letterSpacing: Theme.tracking
renderType: Text.NativeRendering
}
Rectangle {
anchors.left: name.right
anchors.leftMargin: Theme.padS
anchors.right: value.left
anchors.rightMargin: Theme.padS
anchors.verticalCenter: parent.verticalCenter
height: 1
color: Theme.alpha(Theme.outline, 0.4)
}
Text {
id: value
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: Math.min(implicitWidth, row.width * 0.4)
text: row.detail
color: Theme.muted
elide: Text.ElideRight
horizontalAlignment: Text.AlignRight
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
renderType: Text.NativeRendering
}
}