feat: HoneyOS – Retro-Schreibtisch und Leiste für die eigenen Apps
Statische Oberfläche vor Aether und InkNote: kein Backend, kein Build, nginx liefert web/ aus. Jede App bleibt ihre eigene PWA auf ihrer eigenen Domain – HoneyOS ist Startpunkt und Umschalter, kein iframe-Rahmen. - zwei Betriebsarten aus einem Datenstand: Terminal (Kacheln, Tastatur) und Desktop (Symbole, verschiebbare Fenster, Taskleiste) - honeybar.js als Drop-in für die Apps, rendert im Shadow DOM, damit Tailwind und die InkNote-Palette einander nicht in die Quere kommen - Konfiguration vollständig aus der .env: das Skript in /docker-entrypoint.d/ schreibt config.json beim Containerstart - optionaler _ME-Pfad pro App zeigt an, wer angemeldet ist; ohne CORS bleibt die Anzeige leer, ohne dass etwas kaputtgeht Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
84cc5d619e
17 files changed
+3126
No files matched your search
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"user": "marc",
|
||||
"hostname": "honeyos",
|
||||
"version": "0.1",
|
||||
"apps": [
|
||||
{
|
||||
"id": "aether",
|
||||
"name": "AETHER",
|
||||
"subtitle": "Cloud & Dateien",
|
||||
"url": "https://cloud.honeyy.duckdns.org",
|
||||
"glyph": "cloud",
|
||||
"key": "1",
|
||||
"me": "/api/v1/auth/me"
|
||||
},
|
||||
{
|
||||
"id": "inknote",
|
||||
"name": "INKNOTE",
|
||||
"subtitle": "Notizen & Zeichnen",
|
||||
"url": "https://notes.honeyy.duckdns.org",
|
||||
"glyph": "note",
|
||||
"key": "2",
|
||||
"me": "/api/auth/me",
|
||||
"tokenKey": "inknote_token"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>HoneyBar — Vorschau in einer fremden App</title>
|
||||
<style>
|
||||
/* Bewusst NICHT im HoneyOS-Stil: hier wird geprüft, dass die Leiste
|
||||
eine ganz andere App nicht anfasst und von ihr nicht angefasst wird. */
|
||||
body {
|
||||
margin: 0;
|
||||
background: #0d1117;
|
||||
color: #e6edf3;
|
||||
font: 15px/1.6 system-ui, sans-serif;
|
||||
}
|
||||
header.fake {
|
||||
padding: 14px 20px;
|
||||
background: #161b22;
|
||||
border-bottom: 1px solid #30363d;
|
||||
font-weight: 600;
|
||||
}
|
||||
main { padding: 24px 20px; max-width: 620px; }
|
||||
code { background: #161b22; padding: 2px 5px; border-radius: 4px; font-size: 13px; }
|
||||
/* So rechnet eine App mit Vollhöhe die Leiste heraus: */
|
||||
.full { height: calc(100vh - var(--honeybar-h, 0px)); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="fake">Irgendeine App (dunkles Theme, eigene Schrift)</header>
|
||||
<main>
|
||||
<p>Die Leiste oben kommt aus <code>honeybar.js</code> und gehört nicht zu dieser Seite.</p>
|
||||
<p>
|
||||
Sie liegt in einem Shadow DOM: nichts von hier färbt auf sie ab, nichts von ihr auf
|
||||
diese Seite. <code>data-app="inknote"</code> markiert, wo man gerade ist.
|
||||
</p>
|
||||
<p>Eine App, die mit <code>100vh</code> rechnet, nimmt <code>--honeybar-h</code> ab.</p>
|
||||
</main>
|
||||
|
||||
<script src="./honeybar.js" data-app="inknote" data-home="../index.html"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* HoneyBar — die HoneyOS-Leiste zum Einhängen in eine fremde App.
|
||||
*
|
||||
* <script src="https://os.honeyy.duckdns.org/honeybar/honeybar.js"
|
||||
* data-app="aether"></script>
|
||||
*
|
||||
* Eine Zeile, kein Build, keine Abhängigkeit. Die Leiste rendert in einem
|
||||
* Shadow DOM — die Stile der Gastgeber-App kommen nicht rein, unsere nicht
|
||||
* raus. Das ist der ganze Grund für die Konstruktion: Aether ist React +
|
||||
* Tailwind, InkNote React + eigene Palette, und beide sollen von der Leiste
|
||||
* nichts merken außer den 32 Pixeln oben.
|
||||
*
|
||||
* data-Attribute am <script>:
|
||||
* data-app (Pflicht) id der App, in der die Leiste steckt
|
||||
* data-apps URL zu einer config.json; ohne das gilt die Liste unten
|
||||
* data-home URL des HoneyOS-Desktops
|
||||
* data-push "body" (Standard) schiebt den Seiteninhalt nach unten,
|
||||
* "none" legt die Leiste darüber
|
||||
*
|
||||
* Wichtig zu wissen: localStorage ist pro Domain. Was auf os.honeyy… steht,
|
||||
* sieht diese Leiste auf notes.honeyy… nicht — die Leiste ist ein Schalter,
|
||||
* kein geteilter Zustand. Gemeinsame Sitzung wäre echtes SSO und ist eine
|
||||
* eigene Baustelle.
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
if (window.__honeyBar) return; // zweimal eingebunden ist kein Fehler
|
||||
window.__honeyBar = true;
|
||||
|
||||
const script = document.currentScript;
|
||||
const d = (k, fb) => (script && script.dataset[k]) || fb;
|
||||
|
||||
const BAR_H = 32;
|
||||
const LS_HIDDEN = "honeybar.hidden";
|
||||
|
||||
const DEFAULT_APPS = [
|
||||
{ id: "honeyos", name: "DESKTOP", url: "https://os.honeyy.duckdns.org" },
|
||||
{ id: "aether", name: "AETHER", url: "https://cloud.honeyy.duckdns.org" },
|
||||
{ id: "inknote", name: "INKNOTE", url: "https://notes.honeyy.duckdns.org" },
|
||||
];
|
||||
|
||||
const current = d("app", "");
|
||||
const homeUrl = d("home", "https://os.honeyy.duckdns.org");
|
||||
const pushMode = d("push", "body");
|
||||
// Ohne data-apps holt die Leiste die Konfiguration neben ihrem eigenen
|
||||
// Skript — dieselbe Datei, die der Desktop liest.
|
||||
const appsUrl = d("apps", script ? new URL("../config.json", script.src).href : "");
|
||||
|
||||
let apps = DEFAULT_APPS;
|
||||
|
||||
/* ── Aufbau ───────────────────────────────────────────────────────────── */
|
||||
|
||||
const host = document.createElement("honey-bar");
|
||||
host.style.cssText = [
|
||||
"position:fixed", "top:0", "left:0", "right:0",
|
||||
"height:" + BAR_H + "px",
|
||||
"z-index:2147483000",
|
||||
"display:block",
|
||||
"color-scheme:dark",
|
||||
].join(";");
|
||||
const root = host.attachShadow({ mode: "open" });
|
||||
|
||||
root.innerHTML = `
|
||||
<style>
|
||||
:host, * { box-sizing: border-box; }
|
||||
.bar {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
height: ${BAR_H}px; padding: 0 8px;
|
||||
background: #140d07;
|
||||
border-bottom: 1px solid #6b4718;
|
||||
color: #ffb641;
|
||||
font: 12px/1 ui-monospace, "SF Mono", "DejaVu Sans Mono", Menlo, Consolas, monospace;
|
||||
text-shadow: 0 0 5px rgba(255,182,65,.4);
|
||||
user-select: none;
|
||||
}
|
||||
.bar::after {
|
||||
/* Scanlines, damit die Leiste zum Desktop gehört */
|
||||
content: ""; position: absolute; inset: 0; pointer-events: none;
|
||||
background: repeating-linear-gradient(to bottom, rgba(0,0,0,.13) 0 1px, transparent 1px 3px);
|
||||
}
|
||||
a, button {
|
||||
font: inherit; color: inherit; background: none;
|
||||
border: 1px solid transparent; cursor: pointer;
|
||||
text-decoration: none; padding: 3px 9px; white-space: nowrap;
|
||||
}
|
||||
.home { letter-spacing: .14em; color: #ffb641; padding-left: 2px; }
|
||||
.home b { color: #c8842a; font-weight: 400; }
|
||||
.home:hover { border-color: #6b4718; }
|
||||
.apps { display: flex; gap: 4px; flex: 1; overflow-x: auto; scrollbar-width: none; }
|
||||
.apps::-webkit-scrollbar { display: none; }
|
||||
.app { color: #c8842a; letter-spacing: .08em; }
|
||||
.app:hover { color: #ffb641; border-color: #6b4718; }
|
||||
.app[aria-current="page"] {
|
||||
background: #ffb641; border-color: #ffb641; color: #140d07; text-shadow: none;
|
||||
}
|
||||
.right { display: flex; align-items: center; gap: 8px; }
|
||||
.user { color: #ffb641; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.user[hidden] { display: none; }
|
||||
.clock { color: #c8842a; font-variant-numeric: tabular-nums; }
|
||||
.menu-btn { border-color: #6b4718; line-height: 1; padding: 3px 7px; }
|
||||
.menu-btn:hover { background: #2a1c0c; }
|
||||
.menu {
|
||||
position: absolute; top: ${BAR_H + 2}px; right: 6px; min-width: 190px;
|
||||
background: #140d07; border: 1px solid #6b4718; padding: 4px;
|
||||
display: none; flex-direction: column; gap: 2px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,.6);
|
||||
}
|
||||
.menu[data-open="1"] { display: flex; }
|
||||
.menu a, .menu button { text-align: left; padding: 6px 8px; color: #c8842a; }
|
||||
.menu a:hover, .menu button:hover { background: #2a1c0c; color: #ffb641; }
|
||||
.menu hr { border: 0; border-top: 1px solid #2a1c0c; margin: 3px 0; }
|
||||
.menu .head {
|
||||
padding: 4px 8px; font-size: 10px; letter-spacing: .18em;
|
||||
text-transform: uppercase; color: #6b4718;
|
||||
}
|
||||
@media (max-width: 560px) { .clock { display: none; } }
|
||||
</style>
|
||||
|
||||
<div class="bar">
|
||||
<a class="home" id="home" title="HoneyOS">■ HONEY<b>OS</b></a>
|
||||
<nav class="apps" id="apps"></nav>
|
||||
<div class="right">
|
||||
<span class="user" id="user" hidden></span>
|
||||
<span class="clock" id="clock"></span>
|
||||
<button class="menu-btn" id="menuBtn" aria-haspopup="true" aria-expanded="false">▾</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="menu" id="menu" role="menu">
|
||||
<div class="head">wechseln zu</div>
|
||||
<div id="menuApps"></div>
|
||||
<hr />
|
||||
<button id="hide" role="menuitem">leiste ausblenden</button>
|
||||
</div>`;
|
||||
|
||||
/* ── Verhalten ────────────────────────────────────────────────────────── */
|
||||
|
||||
function render() {
|
||||
const nav = root.getElementById("apps");
|
||||
const menuApps = root.getElementById("menuApps");
|
||||
nav.innerHTML = "";
|
||||
menuApps.innerHTML = "";
|
||||
|
||||
apps.forEach((app) => {
|
||||
if (app.id === "honeyos") return; // steckt schon links im Logo
|
||||
|
||||
const a = document.createElement("a");
|
||||
a.className = "app";
|
||||
a.href = app.url;
|
||||
a.textContent = app.name;
|
||||
if (app.id === current) a.setAttribute("aria-current", "page");
|
||||
nav.appendChild(a);
|
||||
|
||||
const m = document.createElement("a");
|
||||
m.href = app.url;
|
||||
m.setAttribute("role", "menuitem");
|
||||
m.textContent = (app.id === current ? "▸ " : " ") + app.name;
|
||||
menuApps.appendChild(m);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt an, wer angemeldet ist. Die Leiste steckt in der App selbst, der
|
||||
* Aufruf geht also an denselben Ursprung — kein CORS, keine Freigabe nötig.
|
||||
* Antwortet die App nicht wie erwartet, bleibt das Feld leer und niemand
|
||||
* merkt etwas: die Anzeige ist eine Zugabe, keine Zusicherung.
|
||||
*/
|
||||
async function showUser() {
|
||||
const me = apps.find((a) => a.id === current);
|
||||
if (!me || !me.me) return;
|
||||
try {
|
||||
const headers = {};
|
||||
if (me.tokenKey) {
|
||||
const tok = localStorage.getItem(me.tokenKey);
|
||||
if (tok) headers.Authorization = "Bearer " + tok;
|
||||
}
|
||||
const res = await fetch(url(me, me.me), { credentials: "same-origin", cache: "no-store", headers });
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const u = (data && (data.user || data.profile)) || data || {};
|
||||
const name = u.display_name || u.displayName || u.name || u.username || u.preferred_username;
|
||||
if (!name) return;
|
||||
const el = root.getElementById("user");
|
||||
el.textContent = name;
|
||||
el.hidden = false;
|
||||
} catch {
|
||||
/* nicht angemeldet, kein Endpunkt, kein Netz — alles kein Grund zu lärmen */
|
||||
}
|
||||
}
|
||||
|
||||
function url(app, path) {
|
||||
if (!path) return app.url;
|
||||
if (/^https?:\/\//i.test(path)) return path;
|
||||
return app.url.replace(/\/+$/, "") + (path.startsWith("/") ? path : "/" + path);
|
||||
}
|
||||
|
||||
function clock() {
|
||||
root.getElementById("clock").textContent = new Date().toLocaleTimeString("de-DE", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
function mount() {
|
||||
document.body.appendChild(host);
|
||||
|
||||
// Der Seite Platz verschaffen. `--honeybar-h` steht bereit, falls eine App
|
||||
// ihre eigene 100vh-Rechnung anpassen muss (siehe README).
|
||||
document.documentElement.style.setProperty("--honeybar-h", BAR_H + "px");
|
||||
if (pushMode === "body") {
|
||||
const cs = getComputedStyle(document.body);
|
||||
document.body.style.paddingTop = parseFloat(cs.paddingTop || 0) + BAR_H + "px";
|
||||
}
|
||||
|
||||
render();
|
||||
clock();
|
||||
setInterval(clock, 20000);
|
||||
|
||||
root.getElementById("home").href = homeUrl;
|
||||
|
||||
const menu = root.getElementById("menu");
|
||||
const btn = root.getElementById("menuBtn");
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
const open = menu.dataset.open === "1";
|
||||
menu.dataset.open = open ? "0" : "1";
|
||||
btn.setAttribute("aria-expanded", String(!open));
|
||||
});
|
||||
// Klick daneben schließt — das Ereignis muss die Grenze des Shadow DOM
|
||||
// in beide Richtungen überleben, deshalb beide Wurzeln.
|
||||
const close = () => {
|
||||
menu.dataset.open = "0";
|
||||
btn.setAttribute("aria-expanded", "false");
|
||||
};
|
||||
document.addEventListener("click", close);
|
||||
root.addEventListener("click", (e) => {
|
||||
if (!e.composedPath().includes(menu) && e.target !== btn) close();
|
||||
});
|
||||
|
||||
root.getElementById("hide").addEventListener("click", () => {
|
||||
try {
|
||||
localStorage.setItem(LS_HIDDEN, "1");
|
||||
} catch {
|
||||
/* dann eben nur für diesen Aufruf */
|
||||
}
|
||||
unmount();
|
||||
});
|
||||
}
|
||||
|
||||
function unmount() {
|
||||
host.remove();
|
||||
if (pushMode === "body") {
|
||||
const cs = getComputedStyle(document.body);
|
||||
document.body.style.paddingTop = Math.max(0, parseFloat(cs.paddingTop || 0) - BAR_H) + "px";
|
||||
}
|
||||
document.documentElement.style.removeProperty("--honeybar-h");
|
||||
}
|
||||
|
||||
async function start() {
|
||||
let hidden = false;
|
||||
try {
|
||||
hidden = localStorage.getItem(LS_HIDDEN) === "1";
|
||||
} catch {
|
||||
/* kein Speicher, Leiste bleibt sichtbar */
|
||||
}
|
||||
if (hidden) return;
|
||||
|
||||
if (appsUrl) {
|
||||
try {
|
||||
const res = await fetch(appsUrl, { cache: "no-store" });
|
||||
if (res.ok) {
|
||||
const cfg = await res.json();
|
||||
if (Array.isArray(cfg.apps) && cfg.apps.length) {
|
||||
apps = [{ id: "honeyos", name: "DESKTOP", url: homeUrl }].concat(cfg.apps);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* Liste nicht erreichbar → eingebaute Liste, Leiste funktioniert trotzdem */
|
||||
}
|
||||
}
|
||||
|
||||
if (document.body) mount();
|
||||
else document.addEventListener("DOMContentLoaded", mount, { once: true });
|
||||
|
||||
showUser();
|
||||
}
|
||||
|
||||
start();
|
||||
})();
|
||||
@@ -0,0 +1,86 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<meta name="theme-color" content="#0a0705" />
|
||||
<title>HoneyOS</title>
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' fill='%230a0705'/%3E%3Cpath d='M4 4h2v3h4V4h2v8h-2V9H6v3H4z' fill='%23ffb641'/%3E%3C/svg%3E" />
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- Bildschirm: alles liegt darin, damit Scanlines/Vignette darüber passen -->
|
||||
<div class="crt" id="screen">
|
||||
<!-- ── Boot ───────────────────────────────────────────────────────── -->
|
||||
<div class="boot" id="boot" hidden>
|
||||
<pre class="boot-log" id="bootLog" aria-live="polite"></pre>
|
||||
<p class="boot-hint">[ beliebige Taste = überspringen ]</p>
|
||||
</div>
|
||||
|
||||
<!-- ── Desktop ────────────────────────────────────────────────────── -->
|
||||
<div class="desktop" id="desktop" hidden>
|
||||
<!-- Menüleiste: dieselbe Anatomie wie honeybar.js in den Apps -->
|
||||
<header class="menubar">
|
||||
<button class="mb-logo" id="btnAbout" title="Über HoneyOS">
|
||||
<span class="hex">■</span> HONEY<b>OS</b>
|
||||
</button>
|
||||
<nav class="mb-apps" id="mbApps" aria-label="Anwendungen"></nav>
|
||||
<div class="mb-right">
|
||||
<span class="mb-user" id="mbUser">marc</span>
|
||||
<span class="mb-clock" id="clock">--:--</span>
|
||||
<button class="mb-btn" id="btnSettings" title="Einstellungen (S)">⚙</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="stage" id="stage">
|
||||
<!-- Statusfenster -->
|
||||
<section class="win win-status">
|
||||
<div class="win-bar"><span class="win-title">system</span><span class="win-dots">●●●</span></div>
|
||||
<div class="win-body">
|
||||
<p class="greet" id="greet">Willkommen zurück.</p>
|
||||
<dl class="statlist" id="statList"></dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- App-Kacheln -->
|
||||
<section class="tiles" id="tiles" aria-label="Apps starten"></section>
|
||||
</main>
|
||||
|
||||
<!-- Desktop-Modus: Symbolfläche, Fenster, Taskleiste -->
|
||||
<div class="deskspace" id="deskspace" hidden>
|
||||
<div class="desk-icons" id="deskIcons"></div>
|
||||
<div class="windows" id="windows"></div>
|
||||
</div>
|
||||
|
||||
<footer class="taskbar" id="taskbar" hidden>
|
||||
<button class="task-start" id="btnStart">■ START</button>
|
||||
<div class="task-list" id="taskList"></div>
|
||||
<span class="task-date" id="taskDate"></span>
|
||||
</footer>
|
||||
|
||||
<footer class="statusbar" id="statusbar">
|
||||
<span id="hint">[1] [2] app starten · [M] modus · [S] einstellungen · [?] hilfe</span>
|
||||
<span class="sb-right" id="sbRight"></span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- ── Dialoge ────────────────────────────────────────────────────── -->
|
||||
<div class="modal" id="modal" hidden>
|
||||
<div class="win win-modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
|
||||
<div class="win-bar">
|
||||
<span class="win-title" id="modalTitle">einstellungen</span>
|
||||
<button class="win-close" id="btnClose" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
<div class="win-body" id="modalBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CRT-Effekte: rein dekorativ, liegen über allem -->
|
||||
<div class="scanlines" aria-hidden="true"></div>
|
||||
<div class="vignette" aria-hidden="true"></div>
|
||||
<div class="flicker" aria-hidden="true"></div>
|
||||
</div>
|
||||
|
||||
<script src="./os.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,184 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>HoneyOS — Look: System 7</title>
|
||||
<style>
|
||||
/* ── Mac System 7 ────────────────────────────────────────────────────
|
||||
Hellgrau, 1 Bit, alles mit 1px schwarzer Kante. Keine Farbe außer
|
||||
der Auswahl. Schrift klein und eng — Chicago gibt es nicht mehr,
|
||||
Geneva/Verdana kommt am nächsten. */
|
||||
* { box-sizing: border-box; }
|
||||
html, body { height: 100%; margin: 0; }
|
||||
body {
|
||||
font: 12px/1.35 Geneva, Verdana, "DejaVu Sans", sans-serif;
|
||||
color: #000;
|
||||
/* der geditherte Schreibtisch: 2px-Schachbrett */
|
||||
background: repeating-conic-gradient(#8a8a8a 0 25%, #d0d0d0 0 50%) 0 0 / 3px 3px;
|
||||
overflow: hidden;
|
||||
-webkit-font-smoothing: none;
|
||||
}
|
||||
|
||||
/* Menüleiste */
|
||||
.menubar {
|
||||
display: flex; align-items: center; gap: 18px;
|
||||
height: 21px; padding: 0 10px;
|
||||
background: #fff; border-bottom: 1px solid #000;
|
||||
position: relative; z-index: 10;
|
||||
}
|
||||
.menubar .apple { font-weight: 700; letter-spacing: -0.5px; }
|
||||
.menubar .item { cursor: default; }
|
||||
.menubar .item:hover { background: #000; color: #fff; }
|
||||
.menubar .right { margin-left: auto; display: flex; gap: 14px; align-items: center; }
|
||||
|
||||
/* Fenster */
|
||||
.win {
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
border: 1px solid #000;
|
||||
box-shadow: 2px 2px 0 rgba(0,0,0,.45);
|
||||
}
|
||||
.titlebar {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
height: 19px; padding: 0 5px;
|
||||
border-bottom: 1px solid #000;
|
||||
/* die sechs Streifen links und rechts des Titels */
|
||||
background: repeating-linear-gradient(to bottom, #000 0 1px, #fff 1px 2px);
|
||||
}
|
||||
.titlebar .box {
|
||||
width: 11px; height: 11px; background: #fff; border: 1px solid #000; flex: none;
|
||||
}
|
||||
.titlebar .name {
|
||||
background: #fff; padding: 0 7px; font-weight: 700; white-space: nowrap;
|
||||
margin: 0 auto; letter-spacing: -0.2px;
|
||||
}
|
||||
.win-body { padding: 10px 12px; }
|
||||
|
||||
/* Statusfenster */
|
||||
#status { left: 26px; top: 44px; width: 268px; }
|
||||
#status h1 { font-size: 12px; font-weight: 700; margin: 0 0 8px; }
|
||||
.rows { display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; }
|
||||
.rows span:nth-child(odd) { color: #444; }
|
||||
.rows span:nth-child(even) { font-weight: 700; }
|
||||
.sep { border: 0; border-top: 1px solid #000; margin: 9px 0; }
|
||||
.note { color: #555; font-size: 11px; }
|
||||
|
||||
/* Fenster mit den Programmen — ein Finder-Fenster, kein Kachelraster */
|
||||
#apps { left: 330px; top: 44px; width: 372px; }
|
||||
.shelf { display: flex; gap: 6px; padding: 14px 10px 18px; }
|
||||
.icon {
|
||||
width: 104px; padding: 6px 4px 4px; text-align: center; cursor: default;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.icon svg { display: block; margin: 0 auto 5px; image-rendering: pixelated; }
|
||||
.icon .label { display: inline-block; padding: 0 3px; }
|
||||
.icon:hover .label { background: #000; color: #fff; }
|
||||
.icon.sel svg { filter: invert(1); }
|
||||
.icon.sel .label { background: #000; color: #fff; }
|
||||
.shelf-foot {
|
||||
border-top: 1px solid #000; padding: 2px 8px; font-size: 11px; color: #333;
|
||||
display: flex; justify-content: space-between;
|
||||
}
|
||||
|
||||
/* Leiste, wie sie in Aether/InkNote aussähe */
|
||||
#bar { left: 26px; top: 296px; width: 676px; }
|
||||
.bardemo {
|
||||
display: flex; align-items: center; gap: 14px;
|
||||
height: 21px; padding: 0 8px; background: #fff; border-bottom: 1px solid #000;
|
||||
font-size: 11px;
|
||||
}
|
||||
.bardemo .cur { background: #000; color: #fff; padding: 0 5px; }
|
||||
.bardemo .right { margin-left: auto; }
|
||||
.barhost { padding: 12px; color: #333; font-size: 11px; background: #efefef; }
|
||||
|
||||
/* Papierkorb unten rechts, weil ohne den ist es kein Mac */
|
||||
.trash { position: absolute; right: 30px; bottom: 26px; width: 90px; text-align: center; }
|
||||
.trash .label { background: #d0d0d0; padding: 0 3px; }
|
||||
|
||||
.switch {
|
||||
position: absolute; left: 26px; bottom: 20px;
|
||||
font-size: 11px; background: #d0d0d0; padding: 2px 6px; border: 1px solid #000;
|
||||
}
|
||||
.switch a { color: #000; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="menubar">
|
||||
<span class="apple">◆</span>
|
||||
<span class="item">Ablage</span>
|
||||
<span class="item">Bearbeiten</span>
|
||||
<span class="item">Ansicht</span>
|
||||
<span class="item">Spezial</span>
|
||||
<span class="right"><span class="item">Di. 02:47</span><span class="item">HoneyOS ▾</span></span>
|
||||
</div>
|
||||
|
||||
<!-- Statusfenster -->
|
||||
<div class="win" id="status">
|
||||
<div class="titlebar"><span class="box"></span><span class="name">HoneyOS</span></div>
|
||||
<div class="win-body">
|
||||
<h1>Willkommen zurück, marc.</h1>
|
||||
<div class="rows">
|
||||
<span>Programme</span><span>2</span>
|
||||
<span>erreichbar</span><span>2 von 2</span>
|
||||
<span>Version</span><span>0.1</span>
|
||||
</div>
|
||||
<hr class="sep" />
|
||||
<p class="note">Jedes Programm läuft auf seiner eigenen Domain. HoneyOS öffnet sie nur.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Programme -->
|
||||
<div class="win" id="apps">
|
||||
<div class="titlebar"><span class="box"></span><span class="name">Programme</span><span class="box"></span></div>
|
||||
<div class="shelf">
|
||||
<div class="icon sel">
|
||||
<svg width="40" height="40" viewBox="0 0 20 20" shape-rendering="crispEdges">
|
||||
<path fill="#000" d="M6 5h8v1h1v1h1v1h1v6h-1v1H3v-1H2V9h1V8h1V7h1V6h1z"/>
|
||||
<path fill="#fff" d="M6 6h8v1h1v1h1v5H3V9h1V8h1V7h1z"/>
|
||||
|
||||
</svg>
|
||||
<span class="label">Aether</span>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<svg width="40" height="40" viewBox="0 0 20 20" shape-rendering="crispEdges">
|
||||
<path fill="#000" d="M4 2h12v16H4z"/>
|
||||
<path fill="#fff" d="M5 3h10v14H5z"/>
|
||||
<path fill="#000" d="M7 5h6v1H7zm0 3h6v1H7zm0 3h6v1H7zm0 3h4v1H7z"/>
|
||||
</svg>
|
||||
<span class="label">InkNote</span>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<svg width="40" height="40" viewBox="0 0 20 20" shape-rendering="crispEdges">
|
||||
<path fill="#000" d="M9 4h2v5h5v2h-5v5H9v-5H4V9h5z"/>
|
||||
</svg>
|
||||
<span class="label">hinzufügen</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shelf-foot"><span>3 Objekte</span><span>alles bereit</span></div>
|
||||
</div>
|
||||
|
||||
<!-- Leiste -->
|
||||
<div class="win" id="bar">
|
||||
<div class="titlebar"><span class="box"></span><span class="name">so sähe die Leiste in InkNote aus</span></div>
|
||||
<div class="bardemo">
|
||||
<b>◆ HoneyOS</b>
|
||||
<span>Aether</span>
|
||||
<span class="cur">InkNote</span>
|
||||
<span class="right">marc 02:47</span>
|
||||
</div>
|
||||
<div class="barhost">… hier beginnt die App selbst, mit ihrem eigenen Aussehen.</div>
|
||||
</div>
|
||||
|
||||
<div class="trash">
|
||||
<svg width="40" height="40" viewBox="0 0 20 20" shape-rendering="crispEdges">
|
||||
<path fill="#000" d="M7 3h6v1h4v1H3V4h4zm-2 3h10v12H5z"/>
|
||||
<path fill="#d0d0d0" d="M6 7h8v10H6z"/>
|
||||
<path fill="#000" d="M8 9h1v6H8zm3 0h1v6h-1z"/>
|
||||
</svg>
|
||||
<span class="label">Papierkorb</span>
|
||||
</div>
|
||||
|
||||
<div class="switch">Look: <b>System 7</b> · <a href="../index.html">Amber-CRT</a> · <a href="./win95.html">Windows 95</a></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,225 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>HoneyOS — Look: Windows 95</title>
|
||||
<style>
|
||||
/* ── Windows 95 ──────────────────────────────────────────────────────
|
||||
Alles lebt von zwei Kanten: hell oben/links, dunkel unten/rechts.
|
||||
Das ist der gesamte 3D-Effekt, mehr steckt nicht dahinter. */
|
||||
* { box-sizing: border-box; }
|
||||
html, body { height: 100%; margin: 0; }
|
||||
body {
|
||||
font: 11px/1.4 "MS Sans Serif", Tahoma, Verdana, "DejaVu Sans", sans-serif;
|
||||
color: #000;
|
||||
background: #008080;
|
||||
overflow: hidden;
|
||||
-webkit-font-smoothing: none;
|
||||
}
|
||||
|
||||
.raised {
|
||||
border: 2px outset;
|
||||
border-color: #fff #404040 #404040 #fff;
|
||||
background: #c0c0c0;
|
||||
box-shadow: inset -1px -1px 0 #808080, inset 1px 1px 0 #dfdfdf;
|
||||
}
|
||||
.sunken {
|
||||
border: 2px solid;
|
||||
border-color: #808080 #fff #fff #808080;
|
||||
background: #c0c0c0;
|
||||
box-shadow: inset 1px 1px 0 #404040, inset -1px -1px 0 #dfdfdf;
|
||||
}
|
||||
|
||||
/* Schreibtisch-Symbole */
|
||||
.desk { position: absolute; top: 12px; left: 12px; width: 84px; }
|
||||
.deskicon {
|
||||
width: 84px; padding: 6px 2px; text-align: center; color: #fff; cursor: default;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.deskicon svg { display: block; margin: 0 auto 4px; image-rendering: pixelated; }
|
||||
.deskicon .label { display: inline-block; padding: 1px 3px; text-shadow: 1px 1px 0 rgba(0,0,0,.6); }
|
||||
.deskicon.sel .label { background: #000080; text-shadow: none; outline: 1px dotted #fff; }
|
||||
.deskicon.sel svg { filter: hue-rotate(0deg) brightness(.85); }
|
||||
|
||||
/* Fenster */
|
||||
.win { position: absolute; padding: 3px; }
|
||||
.titlebar {
|
||||
display: flex; align-items: center; gap: 4px;
|
||||
height: 18px; padding: 0 2px 0 3px;
|
||||
background: linear-gradient(90deg, #000080, #1084d0);
|
||||
color: #fff; font-weight: 700;
|
||||
}
|
||||
.titlebar.inactive { background: linear-gradient(90deg, #808080, #b5b5b5); }
|
||||
.titlebar .t { flex: 1; }
|
||||
.tbtn {
|
||||
width: 16px; height: 14px; font: 700 10px/10px "MS Sans Serif", Tahoma, sans-serif;
|
||||
display: grid; place-items: center; padding-bottom: 2px;
|
||||
}
|
||||
.menustrip { display: flex; gap: 12px; padding: 2px 5px; }
|
||||
.menustrip span { cursor: default; }
|
||||
.menustrip span:hover { background: #000080; color: #fff; }
|
||||
.win-body { padding: 10px; }
|
||||
|
||||
#status { left: 120px; top: 30px; width: 292px; }
|
||||
#status h1 { font-size: 11px; margin: 0 0 8px; font-weight: 700; }
|
||||
.rows { display: grid; grid-template-columns: 1fr auto; gap: 3px 10px; margin-bottom: 10px; }
|
||||
.rows span:nth-child(even) { font-weight: 700; }
|
||||
.field { padding: 6px 8px; color: #000; }
|
||||
.note { color: #404040; }
|
||||
.buttons { display: flex; gap: 6px; justify-content: flex-end; margin-top: 12px; }
|
||||
.btn { padding: 3px 14px; min-width: 72px; text-align: center; cursor: default; }
|
||||
.btn.default { outline: 1px solid #000; outline-offset: 1px; }
|
||||
|
||||
#progs { left: 436px; top: 30px; width: 300px; }
|
||||
.listview { padding: 4px; height: 148px; background: #fff; }
|
||||
.row { display: flex; align-items: center; gap: 8px; padding: 3px 4px; }
|
||||
.row.sel { background: #000080; color: #fff; }
|
||||
.row .sub { margin-left: auto; color: #606060; }
|
||||
.row.sel .sub { color: #c0c0c0; }
|
||||
.statusfields { display: flex; gap: 3px; margin-top: 4px; }
|
||||
.statusfields div { padding: 2px 6px; }
|
||||
.statusfields div:first-child { flex: 1; }
|
||||
|
||||
/* Leiste */
|
||||
#bar { left: 120px; top: 236px; width: 616px; }
|
||||
.bardemo {
|
||||
display: flex; align-items: center; gap: 4px; padding: 2px 3px;
|
||||
}
|
||||
.bardemo .pill { padding: 2px 10px; cursor: default; }
|
||||
.bardemo .right { margin-left: auto; display: flex; gap: 4px; align-items: center; }
|
||||
.barhost { margin-top: 3px; padding: 14px 10px; background: #fff; color: #404040; }
|
||||
|
||||
/* Taskleiste */
|
||||
.taskbar {
|
||||
position: absolute; left: 0; right: 0; bottom: 0; height: 28px;
|
||||
display: flex; align-items: center; gap: 4px; padding: 2px 3px;
|
||||
}
|
||||
.start { display: flex; align-items: center; gap: 4px; padding: 2px 8px 2px 4px; font-weight: 700; cursor: default; }
|
||||
.divider { width: 2px; height: 20px; border-left: 1px solid #808080; border-right: 1px solid #fff; margin: 0 2px; }
|
||||
.task { padding: 3px 10px; width: 154px; text-align: left; cursor: default; }
|
||||
.task.pressed {
|
||||
border-color: #808080 #fff #fff #808080;
|
||||
box-shadow: inset 1px 1px 0 #404040;
|
||||
background: repeating-conic-gradient(#c0c0c0 0 25%, #dfdfdf 0 50%) 0 0/2px 2px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.tray { margin-left: auto; display: flex; align-items: center; gap: 6px; padding: 2px 8px; }
|
||||
|
||||
.switch { position: absolute; right: 12px; top: 12px; padding: 4px 8px; }
|
||||
.switch a { color: #000080; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Schreibtisch -->
|
||||
<div class="desk">
|
||||
<div class="deskicon sel">
|
||||
<svg width="32" height="32" viewBox="0 0 16 16" shape-rendering="crispEdges">
|
||||
<path fill="#000" d="M5 4h6v1h1v1h1v1h1v5H2V8h1V7h1V6h1z"/>
|
||||
<path fill="#dfe8f5" d="M5 5h6v1h1v1h1v4H3V8h1V7h1z"/>
|
||||
<path fill="#7fa8d8" d="M4 9h9v2H4z"/>
|
||||
</svg>
|
||||
<span class="label">Aether</span>
|
||||
</div>
|
||||
<div class="deskicon">
|
||||
<svg width="32" height="32" viewBox="0 0 16 16" shape-rendering="crispEdges">
|
||||
<path fill="#000" d="M3 1h10v14H3z"/>
|
||||
<path fill="#fffbe6" d="M4 2h8v12H4z"/>
|
||||
<path fill="#c07000" d="M5 4h6v1H5zm0 3h6v1H5zm0 3h4v1H5z"/>
|
||||
<path fill="#e0b040" d="M10 9h3v1h-3z"/>
|
||||
</svg>
|
||||
<span class="label">InkNote</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statusfenster -->
|
||||
<div class="win raised" id="status">
|
||||
<div class="titlebar">
|
||||
<span class="t">HoneyOS</span>
|
||||
<span class="tbtn raised">_</span><span class="tbtn raised">□</span><span class="tbtn raised">×</span>
|
||||
</div>
|
||||
<div class="menustrip"><span><u>D</u>atei</span><span><u>A</u>nsicht</span><span><u>?</u></span></div>
|
||||
<div class="win-body">
|
||||
<h1>Willkommen zurück, marc.</h1>
|
||||
<div class="rows">
|
||||
<span>Programme</span><span>2</span>
|
||||
<span>erreichbar</span><span>2 von 2</span>
|
||||
<span>Version</span><span>0.1</span>
|
||||
</div>
|
||||
<div class="field sunken note">Jedes Programm läuft auf seiner eigenen Domain.<br />HoneyOS öffnet sie nur.</div>
|
||||
<div class="buttons">
|
||||
<span class="btn raised default">OK</span>
|
||||
<span class="btn raised">Einstellungen</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Programme -->
|
||||
<div class="win raised" id="progs">
|
||||
<div class="titlebar inactive">
|
||||
<span class="t">Programme</span>
|
||||
<span class="tbtn raised">_</span><span class="tbtn raised">□</span><span class="tbtn raised">×</span>
|
||||
</div>
|
||||
<div class="win-body" style="padding: 4px">
|
||||
<div class="listview sunken">
|
||||
<div class="row sel">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" shape-rendering="crispEdges">
|
||||
<path fill="#000" d="M5 4h6v1h1v1h1v1h1v5H2V8h1V7h1V6h1z"/><path fill="#dfe8f5" d="M5 5h6v1h1v1h1v4H3V8h1V7h1z"/>
|
||||
</svg>
|
||||
Aether <span class="sub">Cloud & Dateien</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" shape-rendering="crispEdges">
|
||||
<path fill="#000" d="M3 1h10v14H3z"/><path fill="#fffbe6" d="M4 2h8v12H4z"/><path fill="#c07000" d="M5 4h6v1H5zm0 3h6v1H5z"/>
|
||||
</svg>
|
||||
InkNote <span class="sub">Notizen</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" shape-rendering="crispEdges">
|
||||
<path fill="#404040" d="M7 3h2v4h4v2H9v4H7V9H3V7h4z"/>
|
||||
</svg>
|
||||
Programm hinzufügen…
|
||||
</div>
|
||||
</div>
|
||||
<div class="statusfields">
|
||||
<div class="sunken">3 Objekte</div>
|
||||
<div class="sunken">alles bereit</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Leiste -->
|
||||
<div class="win raised" id="bar">
|
||||
<div class="titlebar inactive"><span class="t">so sähe die Leiste in InkNote aus</span><span class="tbtn raised">×</span></div>
|
||||
<div class="win-body" style="padding: 4px">
|
||||
<div class="bardemo sunken" style="background:#c0c0c0">
|
||||
<span class="pill raised"><b>HoneyOS</b></span>
|
||||
<span class="divider"></span>
|
||||
<span class="pill raised">Aether</span>
|
||||
<span class="pill raised task pressed" style="width:auto">InkNote</span>
|
||||
<span class="right"><span class="pill sunken">marc</span><span class="pill sunken">02:47</span></span>
|
||||
</div>
|
||||
<div class="barhost sunken">… hier beginnt die App selbst, mit ihrem eigenen Aussehen.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="switch raised">
|
||||
Look: <b>Windows 95</b> · <a href="../index.html">Amber-CRT</a> · <a href="./system7.html">System 7</a>
|
||||
</div>
|
||||
|
||||
<!-- Taskleiste -->
|
||||
<div class="taskbar raised">
|
||||
<span class="start raised">
|
||||
<svg width="16" height="14" viewBox="0 0 8 7" shape-rendering="crispEdges">
|
||||
<path fill="#ff2f2f" d="M0 1h3v2H0z"/><path fill="#2fbf2f" d="M4 0h4v3H4z"/>
|
||||
<path fill="#2f6fff" d="M0 4h3v2H0z"/><path fill="#ffd02f" d="M4 4h4v3H4z"/>
|
||||
</svg>
|
||||
Start
|
||||
</span>
|
||||
<span class="divider"></span>
|
||||
<span class="task raised pressed">HoneyOS</span>
|
||||
<span class="task raised">Programme</span>
|
||||
<span class="tray sunken">02:47</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+764
@@ -0,0 +1,764 @@
|
||||
/*
|
||||
* HoneyOS — Amber-CRT-Desktop.
|
||||
*
|
||||
* Farben laufen ausschließlich über die Phosphor-Variablen unten. Wer eine
|
||||
* neue Röhre will, ergänzt einen [data-phosphor]-Block und fasst sonst nichts an.
|
||||
*/
|
||||
|
||||
/* ── Phosphor ──────────────────────────────────────────────────────────── */
|
||||
:root,
|
||||
[data-phosphor="amber"] {
|
||||
--p: #ffb641; /* Vollton — Text, Rahmen im Fokus */
|
||||
--p-mid: #c8842a; /* halb — Sekundärtext */
|
||||
--p-dim: #6b4718; /* schwach — Rahmen, Trennlinien */
|
||||
--p-faint: #2a1c0c; /* Andeutung — Füllungen */
|
||||
--bg: #0a0705;
|
||||
--bg-raised: #140d07;
|
||||
--glow: 0 0 6px rgba(255, 182, 65, 0.45);
|
||||
}
|
||||
[data-phosphor="green"] {
|
||||
--p: #5cff8f;
|
||||
--p-mid: #2fa85c;
|
||||
--p-dim: #17512c;
|
||||
--p-faint: #08200f;
|
||||
--bg: #040803;
|
||||
--bg-raised: #071206;
|
||||
--glow: 0 0 6px rgba(92, 255, 143, 0.4);
|
||||
}
|
||||
[data-phosphor="ice"] {
|
||||
--p: #cfe8ff;
|
||||
--p-mid: #7fa6c4;
|
||||
--p-dim: #395366;
|
||||
--p-faint: #12202a;
|
||||
--bg: #04070a;
|
||||
--bg-raised: #0a1118;
|
||||
--glow: 0 0 6px rgba(207, 232, 255, 0.35);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Boot- und Desktop-Ebene tragen `display:flex` — das schlägt das
|
||||
hidden-Attribut des Browsers, beide lägen sonst gleichzeitig da. */
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--p);
|
||||
font-family: ui-monospace, "SF Mono", "Cascadia Mono", "DejaVu Sans Mono", Menlo, Consolas, monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── Bildschirm + Effekte ──────────────────────────────────────────────── */
|
||||
.crt {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
/* Sanftes Glimmen aus der Mitte, wie eine aufgewärmte Röhre */
|
||||
background:
|
||||
radial-gradient(120% 90% at 50% 45%, color-mix(in srgb, var(--p) 7%, transparent), transparent 70%),
|
||||
var(--bg);
|
||||
text-shadow: var(--glow);
|
||||
}
|
||||
|
||||
.scanlines,
|
||||
.vignette,
|
||||
.flicker {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 90;
|
||||
}
|
||||
|
||||
.scanlines {
|
||||
background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, 0.28) 0 1px, transparent 1px 3px);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.vignette {
|
||||
background: radial-gradient(115% 105% at 50% 50%, transparent 55%, rgba(0, 0, 0, 0.75) 100%);
|
||||
}
|
||||
|
||||
.flicker {
|
||||
background: var(--p);
|
||||
opacity: 0;
|
||||
mix-blend-mode: overlay;
|
||||
animation: flicker 7s infinite steps(1);
|
||||
}
|
||||
|
||||
@keyframes flicker {
|
||||
0%, 96%, 100% { opacity: 0; }
|
||||
97% { opacity: 0.035; }
|
||||
98% { opacity: 0; }
|
||||
99% { opacity: 0.02; }
|
||||
}
|
||||
|
||||
/* Wer die Effekte abschaltet, schaltet sie wirklich ab. */
|
||||
[data-scanlines="off"] .scanlines,
|
||||
[data-flicker="off"] .flicker {
|
||||
display: none;
|
||||
}
|
||||
[data-glow="off"] .crt {
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.flicker { animation: none; }
|
||||
}
|
||||
|
||||
/* ── Boot ──────────────────────────────────────────────────────────────── */
|
||||
.boot {
|
||||
flex: 1;
|
||||
padding: clamp(16px, 5vw, 56px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.boot-log {
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
white-space: pre-wrap;
|
||||
color: var(--p);
|
||||
}
|
||||
|
||||
.boot-log .ok { color: var(--p); }
|
||||
.boot-log .warn { color: var(--p-mid); }
|
||||
.boot-log b { font-weight: 700; }
|
||||
|
||||
.boot-hint {
|
||||
margin-top: auto;
|
||||
color: var(--p-dim);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.caret::after {
|
||||
content: "\2588";
|
||||
animation: blink 1s steps(1) infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
/* ── Desktop ───────────────────────────────────────────────────────────── */
|
||||
.desktop {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
animation: warmup 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes warmup {
|
||||
from { opacity: 0; transform: scaleY(0.9); filter: brightness(2.5); }
|
||||
to { opacity: 1; transform: none; filter: none; }
|
||||
}
|
||||
|
||||
/* Menüleiste — Zwilling von honeybar.js */
|
||||
.menubar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 10px;
|
||||
height: 34px;
|
||||
flex: 0 0 34px;
|
||||
border-bottom: 1px solid var(--p-dim);
|
||||
background: var(--bg-raised);
|
||||
padding-top: env(safe-area-inset-top);
|
||||
height: calc(34px + env(safe-area-inset-top));
|
||||
flex-basis: calc(34px + env(safe-area-inset-top));
|
||||
}
|
||||
|
||||
.mb-logo {
|
||||
background: none;
|
||||
border: 0;
|
||||
color: var(--p);
|
||||
font: inherit;
|
||||
letter-spacing: 0.14em;
|
||||
cursor: pointer;
|
||||
padding: 2px 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mb-logo b { font-weight: 400; color: var(--p-mid); }
|
||||
.mb-logo .hex { font-size: 10px; vertical-align: 2px; }
|
||||
|
||||
.mb-apps {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
flex: 1;
|
||||
}
|
||||
.mb-apps::-webkit-scrollbar { display: none; }
|
||||
|
||||
.mb-app {
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
color: var(--p-mid);
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.08em;
|
||||
padding: 2px 9px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mb-app:hover { color: var(--p); border-color: var(--p-dim); }
|
||||
.mb-app[aria-current="true"] {
|
||||
color: var(--bg);
|
||||
background: var(--p);
|
||||
border-color: var(--p);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.mb-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--p-mid);
|
||||
font-size: 12px;
|
||||
}
|
||||
.mb-user { opacity: 0.9; }
|
||||
.mb-clock { font-variant-numeric: tabular-nums; color: var(--p); }
|
||||
|
||||
.mb-btn {
|
||||
background: none;
|
||||
border: 1px solid var(--p-dim);
|
||||
color: var(--p);
|
||||
font: inherit;
|
||||
line-height: 1;
|
||||
padding: 3px 7px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mb-btn:hover { background: var(--p-faint); }
|
||||
|
||||
/* Bühne */
|
||||
.stage {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: clamp(14px, 3vw, 32px);
|
||||
display: grid;
|
||||
gap: clamp(14px, 3vw, 28px);
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
align-content: start;
|
||||
max-width: 1100px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media (min-width: 860px) {
|
||||
.stage {
|
||||
grid-template-columns: 320px minmax(0, 1fr);
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fenster */
|
||||
.win {
|
||||
border: 1px solid var(--p-dim);
|
||||
background: var(--bg-raised);
|
||||
}
|
||||
|
||||
.win-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 3px 8px;
|
||||
border-bottom: 1px solid var(--p-dim);
|
||||
background: var(--p-faint);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
color: var(--p-mid);
|
||||
}
|
||||
.win-dots { letter-spacing: 2px; opacity: 0.5; }
|
||||
.win-close {
|
||||
background: none;
|
||||
border: 0;
|
||||
color: var(--p);
|
||||
font: inherit;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.win-body { padding: 14px; }
|
||||
|
||||
.greet {
|
||||
margin: 0 0 12px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.statlist {
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 3px 12px;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
.statlist dt { color: var(--p-dim); }
|
||||
.statlist dd { margin: 0; color: var(--p-mid); text-align: right; font-variant-numeric: tabular-nums; }
|
||||
.statlist dd.up { color: var(--p); }
|
||||
.statlist dd.down { color: var(--p-mid); opacity: 0.6; }
|
||||
|
||||
/* App-Kacheln */
|
||||
.tiles {
|
||||
display: grid;
|
||||
gap: clamp(12px, 2.5vw, 20px);
|
||||
grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.tile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 16px;
|
||||
min-height: 168px;
|
||||
border: 1px solid var(--p-dim);
|
||||
background: var(--bg-raised);
|
||||
color: var(--p);
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: background 0.12s, border-color 0.12s, transform 0.12s;
|
||||
}
|
||||
.tile:hover,
|
||||
.tile:focus-visible {
|
||||
background: var(--p-faint);
|
||||
border-color: var(--p);
|
||||
transform: translateY(-2px);
|
||||
outline: none;
|
||||
}
|
||||
.tile:active { transform: none; }
|
||||
|
||||
.tile-key {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 10px;
|
||||
font-size: 11px;
|
||||
color: var(--p-dim);
|
||||
}
|
||||
|
||||
.tile-icon {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
color: var(--p);
|
||||
}
|
||||
.tile-icon svg { display: block; width: 100%; height: 100%; }
|
||||
|
||||
.tile-name {
|
||||
font-size: 16px;
|
||||
letter-spacing: 0.16em;
|
||||
}
|
||||
.tile-sub {
|
||||
font-size: 12px;
|
||||
color: var(--p-mid);
|
||||
margin-top: -6px;
|
||||
}
|
||||
|
||||
.tile-state {
|
||||
margin-top: auto;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--p-dim);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.tile-state .led {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
background: var(--p-dim);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.tile-state[data-state="up"] { color: var(--p); }
|
||||
.tile-state[data-state="up"] .led { background: var(--p); box-shadow: var(--glow); }
|
||||
.tile-state[data-state="down"] { color: var(--p-mid); }
|
||||
.tile-state[data-state="down"] .led { background: transparent; border: 1px solid var(--p-mid); }
|
||||
|
||||
/* leerer Zustand */
|
||||
.tile-add {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--p-dim);
|
||||
border-style: dashed;
|
||||
min-height: 168px;
|
||||
}
|
||||
|
||||
/* Statuszeile */
|
||||
.statusbar {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 4px 10px;
|
||||
padding-bottom: calc(4px + env(safe-area-inset-bottom));
|
||||
border-top: 1px solid var(--p-dim);
|
||||
background: var(--bg-raised);
|
||||
color: var(--p-dim);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.sb-right { color: var(--p-mid); white-space: nowrap; }
|
||||
|
||||
@media (max-width: 620px) {
|
||||
.statusbar { display: none; }
|
||||
}
|
||||
|
||||
/* ── Modal ─────────────────────────────────────────────────────────────── */
|
||||
.modal {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 80;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 16px;
|
||||
background: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
.modal[hidden] { display: none; }
|
||||
|
||||
.win-modal {
|
||||
width: min(520px, 100%);
|
||||
max-height: min(78vh, 640px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 0 0 1px var(--p-faint), 0 18px 60px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
.win-modal .win-body { overflow-y: auto; }
|
||||
|
||||
.field {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.field > .label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
color: var(--p-dim);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.seg { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.seg button {
|
||||
background: none;
|
||||
border: 1px solid var(--p-dim);
|
||||
color: var(--p-mid);
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
padding: 4px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.seg button:hover { border-color: var(--p); color: var(--p); }
|
||||
.seg button[aria-pressed="true"] {
|
||||
background: var(--p);
|
||||
border-color: var(--p);
|
||||
color: var(--bg);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.field input[type="text"],
|
||||
.field input[type="url"] {
|
||||
width: 100%;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--p-dim);
|
||||
color: var(--p);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
.field input:focus {
|
||||
outline: none;
|
||||
border-color: var(--p);
|
||||
}
|
||||
|
||||
.url-row {
|
||||
display: grid;
|
||||
grid-template-columns: 88px 1fr;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.url-row span { font-size: 12px; color: var(--p-mid); letter-spacing: 0.1em; }
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 18px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--p-faint);
|
||||
}
|
||||
.btn {
|
||||
background: none;
|
||||
border: 1px solid var(--p-dim);
|
||||
color: var(--p);
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.1em;
|
||||
padding: 5px 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn:hover { background: var(--p-faint); border-color: var(--p); }
|
||||
.btn-primary { background: var(--p); border-color: var(--p); color: var(--bg); text-shadow: none; }
|
||||
.btn-primary:hover { background: var(--p); filter: brightness(1.1); }
|
||||
|
||||
.about p { margin: 0 0 10px; color: var(--p-mid); font-size: 13px; }
|
||||
.about code {
|
||||
color: var(--p);
|
||||
background: var(--p-faint);
|
||||
padding: 1px 4px;
|
||||
}
|
||||
.keys {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 4px 14px;
|
||||
font-size: 12.5px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.keys dt { color: var(--p); }
|
||||
.keys dd { margin: 0; color: var(--p-mid); }
|
||||
|
||||
/* ── Desktop-Modus ─────────────────────────────────────────────────────────
|
||||
* Dieselbe Röhre, andere Betriebsart: Symbole auf der Fläche, echte Fenster,
|
||||
* Taskleiste unten. Die Menüleiste oben bleibt in beiden Modi dieselbe — sie
|
||||
* ist die Klammer, die HoneyOS mit den Apps teilt.
|
||||
*/
|
||||
.deskspace {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.desk-icons {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
padding: 12px 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.deskicon {
|
||||
width: 94px;
|
||||
padding: 8px 4px 6px;
|
||||
text-align: center;
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
color: var(--p);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
.deskicon:hover { border-color: var(--p-dim); }
|
||||
.deskicon:focus-visible { outline: 1px dotted var(--p); outline-offset: -3px; }
|
||||
|
||||
.di-glyph {
|
||||
/* inline-Elemente ignorieren Breite und Höhe — ohne block wächst das SVG
|
||||
auf seine Eigengröße und sprengt das Symbol. */
|
||||
display: block;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin: 0 auto 6px;
|
||||
color: var(--p);
|
||||
}
|
||||
.di-glyph svg { display: block; width: 100%; height: 100%; }
|
||||
|
||||
.di-label {
|
||||
display: inline-block;
|
||||
font-size: 11.5px;
|
||||
padding: 1px 4px;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.deskicon[data-sel="1"] { background: var(--p-faint); border-color: var(--p-dim); }
|
||||
.deskicon[data-sel="1"] .di-label { background: var(--p); color: var(--bg); text-shadow: none; }
|
||||
|
||||
/* Erreichbarkeitslampe am Symbol */
|
||||
.deskicon[data-app] .di-label::after { content: ""; }
|
||||
.deskicon[data-state="down"] .di-glyph { opacity: 0.45; }
|
||||
|
||||
/* Fenster im Desktop-Modus */
|
||||
.windows {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dwin {
|
||||
position: absolute;
|
||||
pointer-events: auto;
|
||||
border: 1px solid var(--p-dim);
|
||||
background: var(--bg-raised);
|
||||
box-shadow: 0 12px 34px rgba(0, 0, 0, 0.55);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100% - 16px);
|
||||
}
|
||||
.dwin[data-focus="1"] { border-color: var(--p); }
|
||||
.dwin[data-min="1"] { display: none; }
|
||||
|
||||
.dwin .win-bar {
|
||||
cursor: grab;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
}
|
||||
.dwin[data-drag="1"] .win-bar { cursor: grabbing; }
|
||||
.dwin[data-focus="0"] .win-bar { color: var(--p-dim); }
|
||||
|
||||
.dwin .win-body {
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.win-btns { display: flex; gap: 2px; }
|
||||
.win-btn {
|
||||
background: none;
|
||||
border: 1px solid var(--p-dim);
|
||||
color: var(--p);
|
||||
font: inherit;
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
padding: 1px 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.win-btn:hover { background: var(--p-faint); border-color: var(--p); }
|
||||
|
||||
/* Programme-Fenster */
|
||||
.applist { display: flex; flex-direction: column; gap: 2px; }
|
||||
.applist button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
color: var(--p);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
padding: 5px 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.applist button:hover { background: var(--p-faint); border-color: var(--p-dim); }
|
||||
.applist .al-glyph { width: 18px; height: 18px; flex: none; }
|
||||
.applist .al-glyph svg { display: block; width: 100%; height: 100%; }
|
||||
.applist .al-sub { margin-left: auto; color: var(--p-dim); font-size: 11px; }
|
||||
|
||||
/* Taskleiste */
|
||||
.taskbar {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 3px 6px;
|
||||
padding-bottom: calc(3px + env(safe-area-inset-bottom));
|
||||
border-top: 1px solid var(--p-dim);
|
||||
background: var(--bg-raised);
|
||||
}
|
||||
|
||||
.task-start {
|
||||
background: var(--p-faint);
|
||||
border: 1px solid var(--p-dim);
|
||||
color: var(--p);
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.16em;
|
||||
padding: 3px 10px;
|
||||
cursor: pointer;
|
||||
flex: none;
|
||||
}
|
||||
.task-start:hover, .task-start[aria-expanded="true"] { background: var(--p); color: var(--bg); text-shadow: none; }
|
||||
|
||||
.task-list {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.task-list::-webkit-scrollbar { display: none; }
|
||||
|
||||
.task-btn {
|
||||
background: none;
|
||||
border: 1px solid var(--p-dim);
|
||||
color: var(--p-mid);
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
padding: 3px 10px;
|
||||
min-width: 96px;
|
||||
max-width: 160px;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
}
|
||||
.task-btn:hover { color: var(--p); }
|
||||
.task-btn[data-active="1"] { background: var(--p-faint); color: var(--p); border-color: var(--p); }
|
||||
|
||||
.task-date { color: var(--p-dim); font-size: 11px; white-space: nowrap; }
|
||||
|
||||
/* Startmenü */
|
||||
.startmenu {
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
bottom: 4px;
|
||||
min-width: 208px;
|
||||
z-index: 70;
|
||||
border: 1px solid var(--p);
|
||||
background: var(--bg-raised);
|
||||
padding: 4px;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
.startmenu[data-open="1"] { display: flex; }
|
||||
.startmenu .sm-head {
|
||||
padding: 4px 8px;
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--p-dim);
|
||||
}
|
||||
.startmenu button {
|
||||
background: none;
|
||||
border: 0;
|
||||
color: var(--p-mid);
|
||||
font: inherit;
|
||||
font-size: 12.5px;
|
||||
text-align: left;
|
||||
padding: 6px 9px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.startmenu button:hover { background: var(--p); color: var(--bg); text-shadow: none; }
|
||||
.startmenu hr { border: 0; border-top: 1px solid var(--p-faint); margin: 3px 0; }
|
||||
|
||||
@media (max-width: 620px) {
|
||||
.task-btn { min-width: 0; }
|
||||
.task-date { display: none; }
|
||||
}
|
||||
Reference in new issue
Block a user