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

+145 -138
View File
@@ -1,12 +1,16 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import "root:/config"
import "root:/components"
import "root:/services"
// Full-screen power menu. An accent wedge and stripes sweep in behind a stack of leaning
// buttons; Escape or a click on the backdrop dismisses it.
// Full-screen power menu, built like the lock screen: the desktop dims to the
// void, and one small group of type stands in it — the mark, the motto, a
// line about the session, and a NieR menu of six rows. The selected row is a
// pill of light with the text punched out of it; the three that end the
// session light in rust instead. Escape or a click on the dark dismisses it.
PanelWindow {
id: root
@@ -32,16 +36,9 @@ PanelWindow {
property int selectedAction: 0
function focusSelectedAction(): void {
const entry = buttonRepeater.itemAt(root.selectedAction);
if (entry && entry.children.length > 0)
entry.children[0].forceActiveFocus(Qt.TabFocusReason);
}
function moveSelection(delta: int): void {
root.selectedAction = (root.selectedAction + delta + root.actionModel.length)
% root.actionModel.length;
root.focusSelectedAction();
}
readonly property var actionModel: [
@@ -53,6 +50,11 @@ PanelWindow {
{ label: "Shut Down", icon: "󰐥", danger: true, act: "shutdown" }
]
FileView {
id: hostFile
path: "/etc/hostname"
}
onOpenChanged: {
if (open) {
PopupCoordinator.dismissAll();
@@ -64,128 +66,155 @@ PanelWindow {
}
// ------------------------------------------------------------- backdrop
// The desktop stays faintly there behind the dark, deepest on the left
// where the menu stands.
Rectangle {
id: backdrop
anchors.fill: parent
color: Theme.alpha(Theme.base, 0.9)
opacity: 0
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: Theme.alpha(Theme.ink, 0.97) }
GradientStop { position: 0.5; color: Theme.alpha(Theme.ink, 0.93) }
GradientStop { position: 1.0; color: Theme.alpha(Theme.ink, 0.86) }
}
TapHandler {
onTapped: Actions.closePowerMenu()
}
}
// Grid and column, shared with the lock screen.
P5Backdrop {
id: ground
anchors.fill: parent
reveal: 0
stripeOpacity: 0.05
wedgeOpacity: 0.35
wedgeSpan: 0.34
// The live desktop is still visible behind this, so a dot wash would be
// laid over other people's windows rather than over a ground of our
// own. The lock owns the halftone; here it just muddies things.
halftone: false
}
// ---------------------------------------------------------------- title
// ---------------------------------------------------------------- group
Column {
id: title
anchors.left: parent.left
anchors.leftMargin: root.width * 0.12
id: group
x: root.width * 0.14
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -60
spacing: -14
width: 340
opacity: 0
SplitText {
text: "GLORY"
pixelSize: Theme.fsMega
split: 4
}
transform: Translate { id: drift }
SplitText {
text: "TO"
pixelSize: Theme.fsMega
color: Theme.accent
split: 4
}
SplitText {
text: "MANKIND."
pixelSize: Theme.fsMega
split: 4
Porthole {
size: 26
}
Text {
text: "— YORHA"
color: Theme.primary
font.family: Theme.fontMono
font.pixelSize: Theme.fsSmall
font.letterSpacing: 5
topPadding: 24
topPadding: Theme.padM
text: "GLORY TO MANKIND."
color: Theme.accent
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsHuge
font.weight: Font.Light
font.letterSpacing: 6
renderType: Text.NativeRendering
}
}
// -------------------------------------------------------------- actions
Column {
id: actions
anchors.right: parent.right
anchors.rightMargin: root.width * 0.12
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.padM
Text {
topPadding: Theme.padS
leftPadding: 2
text: ["YORHA", (Quickshell.env("USER") || "user") + "@" + hostFile.text().trim(),
Sys.uptime ? "UP " + Sys.uptime : ""].filter(t => t !== "").join(" · ")
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
font.letterSpacing: 2
renderType: Text.NativeRendering
}
// The widest slab in the stack. Fixed rather than derived so the ragged
// x offsets below cannot feed back into the Column's own width.
readonly property int stackWidth: 312
width: stackWidth
Item { width: 1; height: Theme.padXL }
// ------------------------------------------------------------ menu
Repeater {
id: buttonRepeater
model: root.actionModel
// A carrier item owns the staggered slide so the button keeps its
// own hover transform.
Item {
id: entry
required property var modelData
required property int index
readonly property int slabWidth: actions.stackWidth
width: entry.slabWidth
height: 52
readonly property bool selected: root.selectedAction === entry.index
readonly property color tone: entry.modelData.danger ? Theme.alarm : Theme.accent
width: group.width
height: 44
opacity: 0
transform: [
Translate { id: slide; x: 90 },
Rotation {
id: tilt
angle: -3
origin.x: entry.slabWidth / 2
origin.y: 26
}
]
P5Button {
id: actionButton
anchors.fill: parent
text: entry.modelData.label
icon: entry.modelData.icon
destructive: entry.modelData.danger
selected: root.selectedAction === entry.index
accent: entry.modelData.danger ? Theme.alarm : Theme.primary
lean: Theme.skew
onClicked: root.invoke(entry.modelData.act)
onHoveredChanged: if (hovered) root.selectedAction = entry.index
onActiveFocusChanged: if (actionButton.activeFocus) root.selectedAction = entry.index
Keys.forwardTo: [keyController]
transform: Translate { id: rise; y: 6 }
// The divider after the third row: what pauses the session
// above, what ends it below.
Rectangle {
visible: entry.index === 3
anchors.top: parent.top
x: Theme.padL
width: parent.width - Theme.padL * 2
height: 1
color: Theme.alpha(Theme.outline, 0.35)
}
// Staggered entry, one button after another.
Rectangle {
id: slab
anchors.fill: parent
anchors.topMargin: 3
anchors.bottomMargin: 3
radius: height / 2
color: entry.selected ? entry.tone : "transparent"
Behavior on color {
ColorAnimation { duration: Theme.durBase; easing.type: Easing.OutCubic }
}
}
Rectangle {
id: marker
anchors.left: parent.left
anchors.leftMargin: Theme.padL
anchors.verticalCenter: parent.verticalCenter
width: Theme.marker
height: Theme.marker
radius: width / 2
color: entry.selected ? Theme.ink
: (entry.modelData.danger ? Theme.alpha(Theme.alarm, 0.6) : Theme.overlay)
}
Icon {
id: glyph
anchors.left: marker.right
anchors.leftMargin: Theme.padM
anchors.verticalCenter: parent.verticalCenter
// Fixed width so the labels line up whatever the glyph.
width: 20
horizontalAlignment: Text.AlignHCenter
text: entry.modelData.icon
color: entry.selected ? Theme.ink : Theme.muted
font.pixelSize: Theme.fsLarge
}
Text {
anchors.left: glyph.right
anchors.leftMargin: Theme.padM
anchors.verticalCenter: parent.verticalCenter
text: entry.modelData.label.toUpperCase()
color: entry.selected ? Theme.ink : Theme.text
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsBody
font.weight: Theme.weightDisplay
font.letterSpacing: Theme.tracking * 1.5
renderType: Text.NativeRendering
}
HoverHandler {
cursorShape: Qt.PointingHandCursor
onHoveredChanged: if (hovered) root.selectedAction = entry.index
}
TapHandler {
onTapped: root.invoke(entry.modelData.act)
}
// Staggered entry: each row resolves a beat after the last.
Connections {
target: root
function onOpenChanged() {
@@ -196,48 +225,31 @@ PanelWindow {
SequentialAnimation {
id: entryAnim
PauseAnimation { duration: 40 + entry.index * 55 }
PauseAnimation { duration: 120 + entry.index * 40 }
ParallelAnimation {
NumberAnimation {
target: entry; property: "opacity"
to: 1; duration: Theme.durBase
to: 1; duration: Theme.durSlow; easing.type: Easing.OutCubic
}
NumberAnimation {
target: slide; property: "x"
from: 90; to: 0
duration: Theme.durSlow
easing.type: Easing.OutCubic
}
// Slabs land out of true and snap square. Sliding alone
// reads like a slide deck; the tilt is what makes it
// land like a card being thrown down. Same direction
// every time — the motion is the flourish, the geometry
// stays disciplined.
NumberAnimation {
target: tilt; property: "angle"
from: -3; to: 0
duration: Theme.durSlow
easing.type: Easing.OutCubic
easing.overshoot: 2.2
target: rise; property: "y"
from: 6; to: 0; duration: Theme.durSlow; easing.type: Easing.OutCubic
}
}
}
}
}
}
// Hint line.
Text {
anchors.bottom: parent.bottom
anchors.bottomMargin: 40
anchors.horizontalCenter: parent.horizontalCenter
text: "↑ ↓ SELECT ENTER CONFIRM ESC CANCEL"
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
font.letterSpacing: 4
opacity: backdrop.opacity
renderType: Text.NativeRendering
Text {
topPadding: Theme.padXL
leftPadding: Theme.padL
text: "↑ ↓ SELECT ENTER CONFIRM ESC CANCEL"
color: Theme.overlay
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
font.letterSpacing: 2
renderType: Text.NativeRendering
}
}
function invoke(what: string) {
@@ -280,16 +292,15 @@ PanelWindow {
id: openAnim
NumberAnimation {
target: backdrop; property: "opacity"
to: 1; duration: Theme.durBase; easing.type: Easing.OutQuad
}
// One number now drives the slide, the stripe opacity and the wedge.
NumberAnimation {
target: ground; property: "reveal"
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutExpo
to: 1; duration: Theme.durSlow; easing.type: Easing.OutCubic
}
NumberAnimation {
target: title; property: "opacity"
to: 1; duration: Theme.durSlow
target: group; property: "opacity"
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
}
}
@@ -297,14 +308,10 @@ PanelWindow {
id: closeAnim
NumberAnimation {
target: backdrop; property: "opacity"
to: 0; duration: Theme.durFast
to: 0; duration: Theme.durBase; easing.type: Easing.InCubic
}
NumberAnimation {
target: ground; property: "reveal"
to: 0; duration: Theme.durFast
}
NumberAnimation {
target: title; property: "opacity"
target: group; property: "opacity"
to: 0; duration: Theme.durFast
}
}