Add Pinenote Shell widget with control panel
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
# PineNote Shell Widget
|
||||
|
||||
A fullscreen GTK widget designed for Pine64 PineNote running Arch Linux with Hyprland, controllable via hyprgrass gestures.
|
||||
|
||||
## Overview
|
||||
|
||||
This Astal-based GTK application provides:
|
||||
|
||||
- Screen rotation (portrait/landscape)
|
||||
- Display brightness control (100%, 75%, 25%, 0% steps)
|
||||
- Power operations (suspend, shutdown, reboot)
|
||||
- Quick launch shortcuts for terminal, browser, and e-reader
|
||||
- Display refresh functionality
|
||||
- Audio waveform switching
|
||||
|
||||
## Requirements
|
||||
|
||||
- **OS**: Arch Linux with Hyprland
|
||||
- **Desktop Environment**: Astal/GJS environment
|
||||
- **Dependencies**: GJS, GTK 4, hyprgrass
|
||||
- **Display**: Pine64 PineNote (or any compatible monitor)
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```bash
|
||||
aur install ags-node
|
||||
# or for Arch Linux:
|
||||
sudo pacman -S gjs
|
||||
```
|
||||
|
||||
### Clone and Setup
|
||||
|
||||
```bash
|
||||
cd ~/projects/pineNoteShell
|
||||
# Project already set up in pinenote-shell/
|
||||
|
||||
# Install dependencies
|
||||
npm install --prefix ./pinenote-shell
|
||||
cd pinenote-shell && npm install
|
||||
|
||||
# Make widget executable
|
||||
chmod +x ./src/main.js
|
||||
gjs --set-mode=auto-start
|
||||
```
|
||||
|
||||
### Hyprland Configuration
|
||||
|
||||
Add the following to your `~/.config/hypr/hyprgrass.conf`:
|
||||
|
||||
```ini
|
||||
[gesture:toggle_widget]
|
||||
hotkeys = [Mod1, T]
|
||||
shorthand = "pinch open"
|
||||
actions = [
|
||||
{
|
||||
command = "exec ags -t pinenote-shell",
|
||||
timeout = 300,
|
||||
target = "window:pinenote-fullscreen"
|
||||
}
|
||||
]
|
||||
|
||||
[gesture:brightness_up]
|
||||
hotkeys = [Mod1, B]
|
||||
shorthand = "swipe up"
|
||||
actions = [
|
||||
{
|
||||
command = "exec ags --js-evaluate 'globalThis.widget.brightnessController.setBrightness(100)'",
|
||||
timeout = 300,
|
||||
target = "window:pinenote-fullscreen"
|
||||
}
|
||||
]
|
||||
|
||||
[gesture:brightness_down]
|
||||
hotkeys = [Mod1, V]
|
||||
shorthand = "swipe down"
|
||||
actions = [
|
||||
{
|
||||
command = "exec ags --js-evaluate 'globalThis.widget.brightnessController.setBrightness(0)'",
|
||||
timeout = 300,
|
||||
target = "window:pinenote-fullscreen"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Additional Gestures
|
||||
|
||||
Add more gestures for other controls:
|
||||
|
||||
```ini
|
||||
[gesture:rotate_landscape]
|
||||
hotkeys = [Mod1, R]
|
||||
shorthand = "swipe left"
|
||||
actions = [
|
||||
{
|
||||
command = "exec ags --js-evaluate 'globalThis.widget.screenRotator.rotateTo(\"landscape\")'",
|
||||
timeout = 300,
|
||||
target = "window:pinenote-fullscreen"
|
||||
}
|
||||
]
|
||||
|
||||
[gesture:launch_terminal]
|
||||
hotkeys = [Mod1, T]
|
||||
shorthand = "double tap left"
|
||||
actions = [
|
||||
{
|
||||
command = "exec ags --js-evaluate 'globalThis.widget.launcherManager.launch(\"terminal\")'",
|
||||
timeout = 300,
|
||||
target = "window:pinenote-fullscreen"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Controls
|
||||
|
||||
1. **Open/Close**: Activate with hyprgrass gesture (see configuration)
|
||||
2. **Screen Rotation**: Tap landscape/portrait icons to rotate display
|
||||
3. **Brightness**: Click brightness level buttons or use gestures in config
|
||||
4. **System Actions**: Use suspend/shutdown buttons for power operations
|
||||
5. **Launch Apps**: Click terminal, browser, or e-reader shortcuts
|
||||
|
||||
### Advanced Features
|
||||
|
||||
- **Waveform Switching**: Use the waveform control to switch between audio output modes
|
||||
- **Display Refresh**: Force display refresh with the refresh button
|
||||
- **Status Display**: Monitor rotation and brightness levels in footer
|
||||
|
||||
## Configuration
|
||||
|
||||
### Custom Applications
|
||||
|
||||
Edit `src/main.js` and modify the `LauncherManager.launch()` method:
|
||||
|
||||
```javascript
|
||||
const launchers = {
|
||||
terminal: "alacritty", // Your preferred terminal
|
||||
browser: "brave-browser", // Or chromium/firefox/edge
|
||||
"e-reader": "calibre", // Your e-reader application
|
||||
};
|
||||
```
|
||||
|
||||
### Hyprland Integration
|
||||
|
||||
The widget uses hyprctl commands to communicate with:
|
||||
- **Screen rotation**: Monitors current rotation via `hyprland.active.monitor.rotation`
|
||||
- **Display refresh**: Uses `hyprctl keyword reload` for display updates
|
||||
- **Brightness**: Controls through `/sys/class/backlight/` interface
|
||||
|
||||
## Styling
|
||||
|
||||
The widget uses CSS styling loaded from `$HOME/.config/ags/style.css`. You can customize the appearance by modifying this file or creating your own theme.
|
||||
|
||||
Common customization options:
|
||||
- `background-color`: Change widget background
|
||||
- `border-radius`, `margin`: Adjust widget borders and spacing
|
||||
- Icon sets: Use different icon themes for better visual consistency
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Widget Won't Appear
|
||||
|
||||
1. **Check Astal installation**: Ensure gjs and ags are installed correctly
|
||||
2. **Verify hyprgrass config**: Make sure your gesture bindings are correct
|
||||
3. **Debug output**: Run with `gjs -d src/main.js` to see console errors
|
||||
|
||||
### Controls Not Working
|
||||
|
||||
1. **Permission issues**: Some operations may require appropriate permissions
|
||||
2. **Missing applications**: Ensure your preferred terminal/browser exist
|
||||
3. **Hyprland compatibility**: Test basic hyprctl commands manually first
|
||||
|
||||
### Performance Issues
|
||||
|
||||
- The widget is designed to be lightweight and efficient
|
||||
- For best performance on PineNote, consider reducing animation frequency
|
||||
- Monitor system resources with `htop` or similar tools
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential features for future development:
|
||||
- [ ] Multi-monitor support
|
||||
- [ ] Advanced brightness curves (not just discrete steps)
|
||||
- [ ] Custom app launcher configuration
|
||||
- [ ] Audio waveform visualization
|
||||
- [ ] System monitoring widgets
|
||||
- [ ] Integration with PineNote-specific settings
|
||||
|
||||
## License
|
||||
|
||||
This project is provided as-is for your PineNote setup. Use responsibly and test changes before deploying to production hardware.
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "pinenote-shell",
|
||||
"version": "1.0.0",
|
||||
"description": "Fullscreen GTK widget for Pine64 PineNote with Hyprland support",
|
||||
"main": "./src/main.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "gjs src/main.js",
|
||||
"dev": "gjs --interpret src/main.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agi/gtk3": "^0.7.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
//!/usr/bin/env -S gjs -m
|
||||
|
||||
const { Gdk, Gtk } = imports.gi;
|
||||
const { App, Astal, Hyprland, Widget, Utils, GObject, exec, execAsync } = await import('@agi/gtk3');
|
||||
|
||||
class PinenoteShell extends App {
|
||||
constructor() {
|
||||
super({
|
||||
id: "pinenote-shell",
|
||||
name: "pinenote.shell",
|
||||
instance: "pinenote-shell",
|
||||
css: Utils.readFile(`${Utils.userHome}/.config/ags/style.css`),
|
||||
// No hot corner needed - hyprgrass gestures control this
|
||||
});
|
||||
|
||||
this.hyprland = new Hyprland({
|
||||
explicitBlur: true,
|
||||
});
|
||||
|
||||
this.brightnessController = new BrightnessController();
|
||||
this.screenRotator = new ScreenRotator(this.hyprland);
|
||||
this.powerManager = new PowerManager();
|
||||
this.launcherManager = new LauncherManager();
|
||||
this.refreshController = new RefreshController();
|
||||
this.waveformController = new WaveformController();
|
||||
|
||||
this.buildInterface();
|
||||
}
|
||||
|
||||
buildInterface() {
|
||||
const centerBox = Widget.Box({
|
||||
vertical: false,
|
||||
children: [
|
||||
// Screen Rotation Section
|
||||
Widget.Box({
|
||||
className: "section",
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Label({
|
||||
label: "Screen Rotation",
|
||||
className: "title",
|
||||
}),
|
||||
Widget.Box({
|
||||
homogeneous: false,
|
||||
children: [
|
||||
Widget.Button({
|
||||
onClicked: () => this.screenRotator.rotateTo("landscape"),
|
||||
child: Widget.Icon({
|
||||
icon: "view-normal",
|
||||
size: 40,
|
||||
})
|
||||
}),
|
||||
Widget.Button({
|
||||
onClicked: () => this.screenRotator.rotateTo("portrait"),
|
||||
child: Widget.Icon({
|
||||
icon: "camera-image",
|
||||
size: 40,
|
||||
})
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]
|
||||
}),
|
||||
|
||||
// Brightness Control Section
|
||||
Widget.Box({
|
||||
className: "section",
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Label({
|
||||
label: "Display Brightness",
|
||||
className: "title",
|
||||
}),
|
||||
Widget.Box({
|
||||
homogeneous: false,
|
||||
children: this.createBrightnessControls(),
|
||||
}),
|
||||
Widget.LevelBar({
|
||||
value: this.brightnessController.currentBrightness / 100,
|
||||
minValue: 0,
|
||||
maxValue: 1,
|
||||
vertical: true,
|
||||
className: "brightness-levelbar",
|
||||
}),
|
||||
]
|
||||
}),
|
||||
|
||||
// Quick Launch Section
|
||||
Widget.Box({
|
||||
className: "section",
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Label({
|
||||
label: "Launch Applications",
|
||||
className: "title",
|
||||
}),
|
||||
Widget.Box({
|
||||
homogeneous: false,
|
||||
children: [
|
||||
Widget.Button({
|
||||
onClicked: () => this.launcherManager.launch("terminal"),
|
||||
child: Widget.Icon({ icon: "terminal", size: 40 }),
|
||||
}),
|
||||
Widget.Button({
|
||||
onClicked: () => this.launcherManager.launch("browser"),
|
||||
child: Widget.Icon({ icon: "web-browser", size: 40 }),
|
||||
}),
|
||||
Widget.Button({
|
||||
onClicked: () => this.launcherManager.launch("e-reader"),
|
||||
child: Widget.Icon({ icon: "document-open", size: 40 }),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]
|
||||
}),
|
||||
|
||||
// Display & Waveform Controls Section
|
||||
Widget.Box({
|
||||
className: "section",
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Label({
|
||||
label: "Display & Waveform",
|
||||
className: "title",
|
||||
}),
|
||||
Widget.Box({
|
||||
homogeneous: false,
|
||||
children: [
|
||||
// Refresh Controls
|
||||
Widget.Button({
|
||||
onClicked: () => this.refreshController.triggerRefresh(),
|
||||
child: Widget.Icon({ icon: "view-refresh", size: 40 }),
|
||||
}),
|
||||
// Waveform Switch (e-ink display mode)
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Label({
|
||||
label: this.waveformController.currentWaveform,
|
||||
className: "waveform-label",
|
||||
}),
|
||||
Widget.SpinButton({
|
||||
adjustment: new Gtk.Adjustment(0, 0, 3, 1, 1),
|
||||
digits: 0,
|
||||
onValueChanged: (spin) => {
|
||||
this.waveformController.setWaveform(spin.getValue());
|
||||
}
|
||||
}),
|
||||
]
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]
|
||||
}),
|
||||
|
||||
// System Controls Section
|
||||
Widget.Box({
|
||||
className: "section",
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Label({
|
||||
label: "System Actions",
|
||||
className: "title",
|
||||
}),
|
||||
Widget.Box({
|
||||
homogeneous: false,
|
||||
children: [
|
||||
Widget.Button({
|
||||
onClicked: () => this.powerManager.suspend(),
|
||||
child: Widget.Icon({ icon: "system-suspend", size: 40 }),
|
||||
}),
|
||||
Widget.Button({
|
||||
onClicked: () => this.showPowerMenu(),
|
||||
child: Widget.Icon({ icon: "system-shutdown", size: 40 }),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]
|
||||
}),
|
||||
].flat(Infinity), // Flatten nested arrays
|
||||
});
|
||||
|
||||
this.addWindow(
|
||||
Astal.Window({
|
||||
namespace: "fullscreen",
|
||||
name: "pinenote-fullscreen",
|
||||
anchor: [Astal.WindowAnchor.TOP, Astal.WindowAnchor.BOTTOM,
|
||||
Astal.WindowAnchor.LEFT, Astal.WindowAnchor.RIGHT],
|
||||
exclusivity: Astal.Exclusivity.NORMAL,
|
||||
visible: false,
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
cssClasses: ["pinenote-shell-widget"],
|
||||
setup: (box) => {
|
||||
// Click outside content area to close
|
||||
box.get_parent().connect("button-release-event", () => {
|
||||
this.closeWidget();
|
||||
return true;
|
||||
});
|
||||
},
|
||||
children: [
|
||||
// Header
|
||||
Widget.CenterBox({
|
||||
className: "header",
|
||||
startWidget: Widget.Label({
|
||||
label: "PineNote Control Panel",
|
||||
className: "title-big",
|
||||
}),
|
||||
endWidget: Widget.Box({
|
||||
children: [
|
||||
Widget.Button({
|
||||
onClicked: () => this.closeWidget(),
|
||||
child: Widget.Icon({ icon: "window-close", size: 20 }),
|
||||
className: "close-button",
|
||||
})
|
||||
]
|
||||
})
|
||||
}),
|
||||
|
||||
// Main Content
|
||||
centerBox,
|
||||
|
||||
// Footer with status
|
||||
Widget.CenterBox({
|
||||
className: "footer",
|
||||
startWidget: Widget.Label({
|
||||
label: this.createStatusLabel(),
|
||||
className: "status-label",
|
||||
})
|
||||
}),
|
||||
]
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
createBrightnessControls() {
|
||||
const levels = [100, 75, 25, 0];
|
||||
return levels.map((level) => Widget.Button({
|
||||
onClicked: () => this.brightnessController.setBrightness(level),
|
||||
child: Widget.Label({ label: `${level}%` }),
|
||||
className: `brightness-button brightness-${level}`,
|
||||
}));
|
||||
}
|
||||
|
||||
createStatusLabel() {
|
||||
let status = [];
|
||||
|
||||
if (this.hyprland.active.monitor) {
|
||||
const monitor = this.hyprland.active.monitor;
|
||||
status.push(`Rotation: ${monitor.rotation || "auto"}`);
|
||||
}
|
||||
|
||||
status.push(`Brightness: ${this.brightnessController.currentBrightness}%`);
|
||||
|
||||
return status.join(" | ");
|
||||
}
|
||||
|
||||
closeWidget() {
|
||||
const window = this.getWindow("fullscreen", "pinenote-fullscreen");
|
||||
if (window) window.setVisible(false);
|
||||
}
|
||||
|
||||
toggleVisibility() {
|
||||
const window = this.getWindow("fullscreen", "pinenote-fullscreen");
|
||||
if (window) window.setVisible(!window.isVisible());
|
||||
}
|
||||
|
||||
showPowerMenu() {
|
||||
const dialog = new Gtk.MessageDialog({
|
||||
transient_for: null,
|
||||
modal: true,
|
||||
destroy_with_parent: true,
|
||||
message_type: Gtk.MessageType.QUESTION,
|
||||
buttons: Gtk.ButtonsType.YES_NO,
|
||||
default_button: Gtk.ResponseType.NO,
|
||||
secondary_text: "Choose an action:",
|
||||
});
|
||||
|
||||
dialog.add_button("Reboot", Gtk.ResponseType.YES);
|
||||
dialog.add_button("Power Off", Gtk.ResponseType.OK);
|
||||
|
||||
dialog.response.connect((_dialog, response) => {
|
||||
if (response === Gtk.ResponseType.YES) {
|
||||
this.powerManager.reboot();
|
||||
} else if (response === Gtk.ResponseType.OK) {
|
||||
this.powerManager.shutdown();
|
||||
}
|
||||
_dialog.close();
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
class BrightnessController {
|
||||
constructor() {
|
||||
this.currentBrightness = 100;
|
||||
this.updateFromSystem();
|
||||
}
|
||||
|
||||
async updateFromSystem() {
|
||||
try {
|
||||
// Try to read from /sys/class/backlight
|
||||
const backlightFiles = await Utils.execAsync('find /sys/class/backlight -name "brightness" | head -1');
|
||||
if (backlightFiles) {
|
||||
const maxFile = await Utils.execAsync(`grep max_brightness ${backlightFiles}`);
|
||||
const currentFile = await Utils.execAsync(`cat ${backlightFiles}`);
|
||||
|
||||
this.maxBrightness = parseInt(maxFile.split(' ')[1] || 0);
|
||||
this.currentBrightness = Math.round(parseInt(currentFile) / this.maxBrightness * 100);
|
||||
}
|
||||
} catch (err) {
|
||||
// Keep default brightness if cannot read from system
|
||||
this.maxBrightness = 100;
|
||||
}
|
||||
}
|
||||
|
||||
async setBrightness(level) {
|
||||
try {
|
||||
const backlightFiles = await Utils.execAsync('find /sys/class/backlight -name "brightness" | head -1');
|
||||
if (backlightFiles && level > 0) {
|
||||
const brightnessPath = `${backlightFiles}`;
|
||||
await Utils.writeFile(brightnessPath, Math.round(level * this.maxBrightness / 100));
|
||||
}
|
||||
|
||||
this.currentBrightness = level;
|
||||
|
||||
// Send notification via hyprland
|
||||
exec(`notify-send "Brightness set to ${level}%"`);
|
||||
} catch (err) {
|
||||
exec(`notify-send "Failed to set brightness: ${err.message}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ScreenRotator {
|
||||
constructor(hyprland) {
|
||||
this.hyprland = hyprland;
|
||||
}
|
||||
|
||||
async rotateTo(rotation) {
|
||||
try {
|
||||
if (this.hyprland.active.monitor) {
|
||||
const monitor = this.hyprland.active.monitor;
|
||||
let hyprlandRotation;
|
||||
|
||||
switch (rotation) {
|
||||
case "landscape":
|
||||
hyprlandRotation = 0;
|
||||
break;
|
||||
case "portrait":
|
||||
hyprlandRotation = 3; // 270 degrees in Hyprland's coordinate system
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// Use hyprctl to set monitor rotation
|
||||
await Utils.execAsync(`hyprctl keyword -- monitor "${monitor.id}" rotate ${hyprlandRotation}`);
|
||||
|
||||
exec(`notify-send "Screen rotated to ${rotation}"`);
|
||||
}
|
||||
} catch (err) {
|
||||
exec(`notify-send "Failed to rotate screen: ${err.message}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PowerManager {
|
||||
constructor() {}
|
||||
|
||||
async suspend() {
|
||||
try {
|
||||
await Utils.execAsync('systemctl suspend');
|
||||
} catch (err) {
|
||||
exec(`notify-send "Failed to suspend: ${err.message}"`);
|
||||
}
|
||||
}
|
||||
|
||||
async shutdown() {
|
||||
try {
|
||||
await Utils.execAsync('systemctl poweroff');
|
||||
} catch (err) {
|
||||
exec(`notify-send "Failed to power off: ${err.message}"`);
|
||||
}
|
||||
}
|
||||
|
||||
async reboot() {
|
||||
try {
|
||||
await Utils.execAsync('systemctl reboot');
|
||||
} catch (err) {
|
||||
exec(`notify-send "Failed to reboot: ${err.message}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LauncherManager {
|
||||
constructor() {}
|
||||
|
||||
async launch(appType) {
|
||||
const launchers = {
|
||||
terminal: "alacritty", // Or your preferred terminal emulator
|
||||
browser: "brave-browser", // Or chromium/firefox
|
||||
"e-reader": "calibre", // Or your e-reader app
|
||||
};
|
||||
|
||||
try {
|
||||
exec(`nohup ${launchers[appType]} &`);
|
||||
exec(`notify-send "${Utils.toTitleCase(appType)} launched"`);
|
||||
} catch (err) {
|
||||
exec(`notify-send "Failed to launch ${appType}: ${err.message}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RefreshController {
|
||||
constructor() {}
|
||||
|
||||
async triggerRefresh() {
|
||||
try {
|
||||
// Trigger a full e-ink display refresh via pdlc-cli
|
||||
await Utils.execAsync('pdlc-cli --refresh 2>/dev/null');
|
||||
exec(`notify-send "E-ink display refreshed"`);
|
||||
} catch (err) {
|
||||
console.log(`Failed to refresh display: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WaveformController {
|
||||
constructor() {
|
||||
// PineNote uses the PDLC-EPD display controller
|
||||
// Waveforms control the e-ink refresh behavior
|
||||
this.currentWaveform = "gc16"; // Default: balanced quality/speed
|
||||
this.waveforms = ["gc16", "gc42", "a2", "full"];
|
||||
}
|
||||
|
||||
setWaveform(index) {
|
||||
const idx = Math.round(index);
|
||||
if (idx >= 0 && idx < this.waveforms.length) {
|
||||
this.currentWaveform = this.waveforms[idx];
|
||||
exec(`notify-send "E-ink waveform: ${this.currentWaveform}"`);
|
||||
|
||||
// Apply waveform via pdlc-cli (PineNote display controller)
|
||||
try {
|
||||
Utils.execAsync(`pdlc-cli --waveform ${this.currentWaveform} 2>/dev/null || true`);
|
||||
} catch (err) {
|
||||
console.log(`Could not apply waveform: ${this.currentWaveform}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
App.addIcons(`${Utils.userHome}/.config/ags/icons`);
|
||||
new PinenoteShell();
|
||||
@@ -0,0 +1,93 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
window.pinenote-shell-widget {
|
||||
background-color: rgba(20, 20, 30, 0.95);
|
||||
color: #ffffff;
|
||||
font-family: "Ubuntu", "Noto Sans", sans-serif;
|
||||
border-radius: 8px;
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
window.pinenote-shell-widget .title-big {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #61afef;
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
window.pinenote-shell-widget .header {
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
window.pinenote-shell-widget .section {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
padding: 16px;
|
||||
margin: 8px 16px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
window.pinenote-shell-widget .title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #98c379;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
window.pinenote-shell-widget .brightness-levelbar {
|
||||
min-height: 200px;
|
||||
width: 20px;
|
||||
margin: 0 auto;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
window.pinenote-shell-widget .brightness-levelbar.fill-block {
|
||||
background-color: #e5c07b;
|
||||
}
|
||||
|
||||
window.pinenote-shell-widget .footer {
|
||||
padding: 16px;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
font-size: 12px;
|
||||
color: #7f848f;
|
||||
}
|
||||
|
||||
window.pinenote-shell-widget .status-label {
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
window.pinenote-shell-widget button {
|
||||
background-color: rgba(97, 175, 239, 0.2);
|
||||
border: 1px solid #61afef;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin: 8px;
|
||||
color: #ffffff;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
window.pinenote-shell-widget button:hover {
|
||||
background-color: rgba(97, 175, 239, 0.4);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
window.pinenote-shell-widget .brightness-button {
|
||||
padding: 8px;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
window.pinenote-shell-widget .waveform-label {
|
||||
font-size: 14px;
|
||||
color: #c678dd;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
window.pinenote-shell-widget Icon {
|
||||
color: #61afef;
|
||||
}
|
||||
Reference in New Issue
Block a user