various styling tweaks

This commit is contained in:
Martin Kleinschrodt 2021-11-17 09:32:21 +01:00
parent 963bee65ab
commit 8a61b3111f
27 changed files with 538 additions and 500 deletions

View File

@ -62,7 +62,7 @@ body {
/* BORDERS */
--border-width: 1px;
--border-color: var(--color-shade-2);
--border-color: var(--color-shade-1);
--border-radius: 0.5em;
/* INPUT ELEMENTS */
@ -197,8 +197,8 @@ body {
/* VAULT ITEMS LIST */
--items-list-item-border-color: transparent;
--items-list-item-field-border-style: solid;
--items-list-item-border-color: var(--border-color);
--items-list-item-field-border-style: none;
--items-list-item-field-border-width: 1px;
--items-list-item-field-border-color: var(--border-color);
--items-list-item-field-spacing: var(--spacing);

View File

@ -57,7 +57,7 @@ export class FieldElement extends LitElement {
];
if (this._fieldDef.mask) {
actions.push({
actions.unshift({
icon: this._masked ? "show" : "hide",
label: this._masked ? "show" : "hide",
action: () => (this._masked = !this._masked),
@ -175,7 +175,6 @@ export class FieldElement extends LitElement {
}
.name-input {
line-height: 1.2em;
text-transform: uppercase;
background: transparent;
}
@ -191,7 +190,7 @@ export class FieldElement extends LitElement {
}
.value-display {
margin: -0.2em 0.4em 0.4em 0.4em;
margin: 0 0.4em 0.4em 0.4em;
white-space: pre-wrap;
overflow-wrap: break-word;
user-select: text;
@ -212,11 +211,6 @@ export class FieldElement extends LitElement {
cursor: grabbing;
}
.actions {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(5em, 1fr));
}
:host(.dragging) pl-drawer {
display: none;
}
@ -431,10 +425,10 @@ export class FieldElement extends LitElement {
</div>
<pl-drawer class="drawer" collapsed>
<div class="actions">
<div class="end-justifying spacing horizontal layout">
${this._fieldActions.map(
({ icon, action, label }) => html`
<pl-button class="transparent slim" @click=${action}>
<pl-button class="ghost small slim" @click=${action} style="min-width: 7em">
<pl-icon icon=${icon} class="right-margined"></pl-icon>
<div>${label}</div>
</pl-button>

View File

@ -49,7 +49,7 @@ export class Settings extends StateMixin(Routing(View)) {
</header>
<pl-scroller class="stretch">
<div class="centering vertical layout fill">
<pl-generator></pl-generator>
<pl-generator class="padded box"></pl-generator>
</div>
</pl-scroller>
</div>

View File

@ -1,5 +1,5 @@
import { Group } from "@padloc/core/src/org";
import { css, html, LitElement } from "lit";
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { shared } from "../styles";
import "./icon";
@ -9,33 +9,17 @@ export class GroupItem extends LitElement {
@property({ attribute: false })
group: Group;
static styles = [
shared,
css`
.icon {
font-size: 120%;
background: var(--color-shade-1);
border: solid 1px var(--border-color);
border-radius: 100%;
width: 2em;
height: 2em;
}
.tags {
margin-top: 0.2em;
}
`,
];
static styles = [shared];
render() {
return html`
<div class="horizontal spacing center-aligning layout">
<pl-icon class="icon" icon="group"></pl-icon>
<pl-icon class="large" icon="group"></pl-icon>
<div class="stretch">
<div class="bold ellipsis">${this.group.name}</div>
<div class="small">
<div class="small top-half-margined">
<div class="tiny tags">
<div class="tag">
<pl-icon icon="user" class="inline"></pl-icon>

View File

@ -283,10 +283,11 @@ export class GroupView extends Routing(StateMixin(LitElement)) {
</header>
<pl-scroller class="stretch">
<section ?hidden=${!org.groups.length} class="double-margined">
<h2 class="center-aligning horizontal layout">
<div class="large stretch section-header">${$l("Members")}</div>
<pl-button class="slim transparent">
<section class="double-margined box">
<h2 class="center-aligning horizontal layout bg-dark border-bottom semibold">
<div class="padded uppercase stretch">${$l("Members")}</div>
<pl-button class="skinny half-margined transparent">
<pl-icon icon="add"></pl-icon>
</pl-button>
@ -351,11 +352,11 @@ export class GroupView extends Routing(StateMixin(LitElement)) {
</pl-list>
</section>
<section class="double-margined">
<h2 class="center-aligning horizontal layout">
<div class="margined section-header stretch">${$l("Vaults")}</div>
<section class="double-margined box">
<h2 class="center-aligning horizontal layout bg-dark border-bottom semibold">
<div class="padded uppercase stretch">${$l("Vaults")}</div>
<pl-button class="slim transparent">
<pl-button class="skinny half-margined transparent">
<pl-icon icon="add"></pl-icon>
</pl-button>

View File

@ -19,6 +19,7 @@ export class PlIcon extends LitElement {
width: 1.3em;
overflow: hidden;
font-weight: inherit;
font-variant: normal !important;
}
:host(.inline) {

View File

@ -5,7 +5,7 @@ import { formatDateFromNow } from "../lib/util";
import { shared } from "../styles";
import "./icon";
import { customElement, property } from "lit/decorators.js";
import { css, html, LitElement } from "lit";
import { html, LitElement } from "lit";
@customElement("pl-invite-item")
export class InviteItem extends LitElement {
@ -16,19 +16,7 @@ export class InviteItem extends LitElement {
return !!this.invite;
}
static styles = [
shared,
css`
.icon {
font-size: 120%;
background: var(--color-shade-1);
border: solid 1px var(--border-color);
border-radius: 100%;
width: 2em;
height: 2em;
}
`,
];
static styles = [shared];
render() {
const inv = this.invite!;
@ -45,13 +33,17 @@ export class InviteItem extends LitElement {
return html`
<div class="horizontal spacing center-aligning layout">
<pl-icon class="icon" icon="mail"></pl-icon>
<pl-icon class="large" icon="mail"></pl-icon>
<div class="stretch ellipsis">${inv.email}</div>
<div class="stretch">
<div class="ellipsis">${inv.email}</div>
<div class="tiny tag ${status.class}">
<pl-icon icon="${status.icon}" class="inline"></pl-icon>
${until(status.text)}
<div class="small top-half-margined tags">
<div class="tiny tag ${status.class}">
<pl-icon icon="${status.icon}" class="inline"></pl-icon>
${until(status.text)}
</div>
</div>
</div>
</div>
`;

View File

@ -211,7 +211,7 @@ export class ItemView extends Routing(StateMixin(LitElement)) {
.fields,
.attachments {
margin: 0.2em 0.5em;
margin: 0.5em 0;
}
.field-selector {
@ -361,7 +361,7 @@ export class ItemView extends Routing(StateMixin(LitElement)) {
(field) => `${this.itemId}_${field.name}_${field.type}`,
(field: Field, index: number) => html`
<pl-field
class="animated padded list-item ${!this._editing ? "hover" : ""}"
class="animated padded list-item"
.canMoveUp=${!!index}
.canMoveDown=${index < this._fields.length - 1}
.field=${field}
@ -382,9 +382,9 @@ export class ItemView extends Routing(StateMixin(LitElement)) {
</div>
<div class="attachments" ?hidden=${!attachments.length}>
<h2 class="animated section-header horizontal center-aligning center-justifying layout">
<pl-icon icon="attachment" class="small right-margined"></pl-icon>
<div>${$l("Attachments")}</div>
<h2 class="horizontally-double-margined animated section-header">
<pl-icon icon="attachment" class="inline"></pl-icon>
${$l("Attachments")}
</h2>
<pl-list>

View File

@ -142,24 +142,20 @@ export class VaultItemListItem extends LitElement {
:host {
display: block;
pointer-events: none;
}
.item-tags {
margin: 0.7em 0 1em 0;
padding: 0.75em;
}
.item-fields {
position: relative;
display: flex;
overflow-x: auto;
font-size: var(--font-size-small);
-webkit-overflow-scrolling: touch;
/* scroll-snap-type: x proximity; */
/* scroll-padding: 1em; */
margin: 0 -12px;
padding: 0 12px;
scroll-behavior: smooth;
pointer-events: auto;
padding: 0 0.75em;
margin: 0.75em -0.75em 0 -0.75em;
}
.item-field {
@ -169,6 +165,7 @@ export class VaultItemListItem extends LitElement {
border-radius: 0.5em;
max-width: calc(60%);
opacity: 0.999;
font-size: var(--font-size-small);
border-style: var(--items-list-item-field-border-style, solid);
border-width: var(--items-list-item-field-border-width, 1px);
border-color: var(--items-list-item-field-border-color, var(--border-color));
@ -222,6 +219,7 @@ export class VaultItemListItem extends LitElement {
color: var(--color-highlight);
font-weight: var(--items-list-item-field-name-weight, 400);
text-transform: uppercase;
margin-bottom: 0.2em;
${mixins.ellipsis()};
}
@ -246,7 +244,10 @@ export class VaultItemListItem extends LitElement {
z-index: 1;
pointer-events: auto;
--button-background: var(--color-background);
--button-shadow: rgba(0, 0, 0, 0.2) 0 0 5px -1px;
--button-border-style: solid;
--button-border-color: var(--color-foreground);
--button-border-width: 1px;
--button-shadow: rgba(0, 0, 0, 0.5) 0 1px 2px -1px;
}
.move-left-button {
@ -313,25 +314,26 @@ export class VaultItemListItem extends LitElement {
}
return html`
<div class="item-header center-aligning horizontal layout top-half-margined">
<div class="stretch ellipsis semibold" ?disabled=${!item.name}>${item.name || $l("No Name")}</div>
<pl-icon class="small" icon="forward"></pl-icon>
</div>
<div class="margined center-aligning horizontal layout">
<div class="stretch collapse spacing horizontal layout">
<div class="ellipsis semibold stretch collapse" ?disabled=${!item.name}>
${item.name || $l("No Name")}
</div>
<div class="tiny tags item-tags">
${tags.map(
(tag) => html`
<div class="tag ${tag.class} ellipsis">
${tag.icon ? html`<pl-icon icon="${tag.icon}" class="inline"></pl-icon>` : ""}
${tag.name ? html`${tag.name}` : ""}
</div>
`
)}
${tags.map(
(tag) => html`
<div class="tiny tag ${tag.class} ellipsis">
${tag.icon ? html`<pl-icon icon="${tag.icon}" class="inline"></pl-icon>` : ""}
${tag.name ? html`${tag.name}` : ""}
</div>
`
)}
</div>
</div>
<div class="relative">
<pl-button
class="small round slim move-left-button"
class="small round skinny move-left-button"
?invisible=${!this._canScrollLeft}
@click=${this._moveLeft}
>
@ -339,7 +341,7 @@ export class VaultItemListItem extends LitElement {
</pl-button>
<pl-button
class="small round slim move-right-button"
class="small round skinny move-right-button"
?invisible=${!this._canScrollRight}
@click=${this._moveRight}
>
@ -378,8 +380,8 @@ export class VaultItemListItem extends LitElement {
@click=${(e: MouseEvent) => this._openAttachment(a, item, e)}
>
<div class="item-field-label">
<div class="small item-field-name ellipsis">
<pl-icon class="small inline" icon="attachment"></pl-icon>
<div class="tiny item-field-name ellipsis">
<pl-icon class="inline" icon="attachment"></pl-icon>
${a.name}
</div>
<div class="item-field-value">
@ -551,20 +553,10 @@ export class ItemsList extends StateMixin(LitElement) {
flex: 1;
}
.header-icon {
width: 1.3em;
height: 1.3em;
}
.item-header {
padding-left: 0.5em;
margin-bottom: 0.3em;
margin-top: 0.3em;
}
.list-item {
--list-item-border-color: var(--items-list-item-border-color);
/* overflow: hidden; */
border-bottom: solid 1px var(--list-item-border-color);
}
.list-item[aria-selected="true"] {
@ -681,7 +673,7 @@ export class ItemsList extends StateMixin(LitElement) {
class="half-margined fill-horizontally horizontal spacing center-aligning layout text-left-aligning"
>
${heading.iconUrl
? html` <img .src=${heading.iconUrl} class="header-icon" cl /> `
? html` <img .src=${heading.iconUrl} class="header-icon" /> `
: html` <pl-icon icon="${heading.icon}"></pl-icon> `}
<div class="stretch collapse ellipsis">
${heading.superTitle
@ -953,7 +945,7 @@ export class ItemsList extends StateMixin(LitElement) {
role="option"
aria-selected="${selected}"
aria-label="${item.name}"
class="padded horizontally-margined list-item center-aligning horizontal layout"
class="list-item center-aligning horizontal layout"
@click=${() => this.selectItem(li)}
>
<div class="fullbleed click" style="border-radius: inherit"></div>
@ -961,7 +953,7 @@ export class ItemsList extends StateMixin(LitElement) {
this.multiSelect
? html`
<div
class="item-check right-margined ${this._multiSelect.has(item.id) ? "checked" : ""}"
class="item-check left-margined ${this._multiSelect.has(item.id) ? "checked" : ""}"
?hidden=${!this.multiSelect}
></div>
`

View File

@ -41,38 +41,37 @@ export class MemberItem extends LitElement {
<pl-fingerprint .key=${this.member.publicKey}></pl-fingerprint>
<div class="stretch">
<div class="horizontal layout">
<div class="bold stretch ellipsis">${this.member.name}</div>
<div class="bold ellipsis">${this.member.email}</div>
<div class="tiny tags">
${this.hideInfo
? ""
: html`
${groups.length === 1
? html`
<div class="tag">
<pl-icon icon="group" class="inline"></pl-icon>
${groups[0].name}
</div>
`
: html`
<div class="tag">
<pl-icon icon="group" class="inline"></pl-icon>
${groups.length}
</div>
`}
${isOwner
? html` <div class="tag warning">${$l("Owner")}</div> `
: isAdmin
? html` <div class="tag highlight">${$l("Admin")}</div> `
: isSuspended
? html` <div class="tag warning">${$l("Suspended")}</div> `
: ""}
`}
</div>
<div class="small top-half-margined wrapping spacing horizontal layout">
<div>${this.member.name}</div>
${this.hideInfo
? ""
: html`
${groups.length === 1
? html`
<div class="tiny tag">
<pl-icon icon="group" class="inline"></pl-icon>
${groups[0].name}
</div>
`
: html`
<div class="tiny tag">
<pl-icon icon="group" class="inline"></pl-icon>
${groups.length}
</div>
`}
${isOwner
? html` <div class="tiny tag warning">${$l("Owner")}</div> `
: isAdmin
? html` <div class="tiny tag highlight">${$l("Admin")}</div> `
: isSuspended
? html` <div class="tiny tag warning">${$l("Suspended")}</div> `
: ""}
`}
</div>
<div class="ellipsis">${this.member.email}</div>
<div class="small"></div>
</div>
</div>
`;

View File

@ -370,10 +370,11 @@ export class MemberView extends Routing(StateMixin(LitElement)) {
</header>
<pl-scroller class="stretch">
<section ?hidden=${!org.groups.length} class="double-margined">
<h2 class="center-aligning horizontal layout">
<div class="margined section-header stretch">${$l("Groups")}</div>
<pl-button class="slim transparent">
<section ?hidden=${!org.groups.length} class="double-margined box">
<h2 class="center-aligning horizontal layout bg-dark border-bottom semibold">
<div class="padded uppercase stretch">${$l("Groups")}</div>
<pl-button class="skinny half-margined transparent">
<pl-icon icon="add"></pl-icon>
</pl-button>
@ -431,10 +432,11 @@ export class MemberView extends Routing(StateMixin(LitElement)) {
</pl-list>
</section>
<section class="double-margined">
<h2 class="center-aligning horizontal layout">
<div class="margined section-header stretch">${$l("Vaults")}</div>
<pl-button class="slim transparent">
<section class="double-margined box">
<h2 class="center-aligning horizontal layout bg-dark border-bottom semibold">
<div class="padded uppercase stretch">${$l("Vaults")}</div>
<pl-button class="skinny half-margined transparent">
<pl-icon icon="add"></pl-icon>
</pl-button>

View File

@ -124,13 +124,21 @@ export class OrgDashboard extends Routing(StateMixin(LitElement)) {
<pl-scroller class="stretch">
<div class="sections">
<section ?hidden=${!org.invites.length || !org.isOwner(this.app.account!)}>
<section class="box" ?hidden=${!org.invites.length || !org.isOwner(this.app.account!)}>
<h2
class="center-aligning margined section-header spacing center-aligning horizontal layout"
class="uppercase bg-dark border-bottom semibold center-aligning spacing horizontal layout"
>
<pl-icon icon="mail"></pl-icon>
<div>${org.invites.length}</div>
<div>${$l("Pending Invites")}</div>
<div></div>
<div>${$l("Invites")}</div>
<div class="tiny bold tag">${org.invites.length}</div>
<div class="stretch"></div>
<pl-button
class="skinny transparent half-margined"
@click=${this._createInvite}
?disabled=${!org.isOwner(this.app.account)}
>
<pl-icon icon="add"></pl-icon>
</pl-button>
</h2>
<pl-list>
${org.invites.slice(0, 5).map(
@ -154,13 +162,21 @@ export class OrgDashboard extends Routing(StateMixin(LitElement)) {
</pl-button>
</section>
<section>
<section class="box">
<h2
class="center-aligning margined section-header spacing center-aligning horizontal layout"
class="uppercase bg-dark border-bottom semibold center-aligning spacing horizontal layout"
>
<pl-icon icon="members"></pl-icon>
<div>${org.members.length}</div>
<div></div>
<div>${$l("Members")}</div>
<div class="tiny bold tag">${org.members.length}</div>
<div class="stretch"></div>
<pl-button
class="skinny transparent half-margined"
@click=${this._createInvite}
?disabled=${!org.isOwner(this.app.account)}
>
<pl-icon icon="add"></pl-icon>
</pl-button>
</h2>
<pl-list>
${org.members.slice(0, 5).map(
@ -184,13 +200,20 @@ export class OrgDashboard extends Routing(StateMixin(LitElement)) {
</pl-button>
</section>
<section ?hidden=${quota?.groups === 0}>
<section class="box" ?hidden=${quota?.groups === 0}>
<h2
class="center-aligning margined section-header spacing center-aligning horizontal layout"
class="uppercase bg-dark border-bottom semibold center-aligning spacing horizontal layout"
>
<pl-icon icon="group"></pl-icon>
<div>${org.groups.length}</div>
<div></div>
<div>${$l("Groups")}</div>
<div class="tiny bold tag">${org.groups.length}</div>
<div class="stretch"></div>
<pl-button
class="skinny transparent half-margined"
@click=${() => this.go(`orgs/${this.orgId}/vaults/new`)}
>
<pl-icon icon="add"></pl-icon>
</pl-button>
</h2>
${org.groups.length
? html`
@ -234,13 +257,20 @@ export class OrgDashboard extends Routing(StateMixin(LitElement)) {
`}
</section>
<section>
<section class="box">
<h2
class="center-aligning margined section-header spacing center-aligning horizontal layout"
class="uppercase bg-dark border-bottom semibold center-aligning spacing horizontal layout"
>
<pl-icon icon="vault"></pl-icon>
<div>${org.vaults.length}</div>
<div></div>
<div>${$l("Vaults")}</div>
<div class="tiny bold tag">${org.vaults.length}</div>
<div class="stretch"></div>
<pl-button
class="skinny transparent half-margined"
@click=${() => this.go(`orgs/${this.orgId}/vaults/new`)}
>
<pl-icon icon="add"></pl-icon>
</pl-button>
</h2>
${org.vaults.length
? html`

View File

@ -72,7 +72,7 @@ export class OrgGroupsView extends Routing(StateMixin(LitElement)) {
${groups.map(
(group) => html`
<div
class="padded horizontally-margined list-item hover click"
class="double-padded list-item hover click"
aria-selected=${group.name === this.groupName}
@click=${() => this._toggleGroup(group)}
>

View File

@ -133,7 +133,7 @@ export class OrgInvitesView extends Routing(StateMixin(LitElement)) {
${invites.map(
(invite) => html`
<div
class="padded list-item horizontally-margined hover click"
class="double-padded list-item hover click"
aria-selected=${invite.id === this.inviteId}
@click=${() => this._toggleInvite(invite)}
>

View File

@ -132,7 +132,7 @@ export class OrgMembersView extends Routing(StateMixin(LitElement)) {
${members.map(
(member) => html`
<div
class="padded list-item horizontally-margined hover click"
class="double-padded list-item hover click"
aria-selected=${member.id === this.memberId}
@click=${() => this._toggleMember(member)}
>

View File

@ -71,7 +71,7 @@ export class OrgVaultsView extends Routing(StateMixin(LitElement)) {
${vaults.map(
(vault) => html`
<div
class="padded list-item horizontally-margined hover click"
class="double-padded list-item hover click"
aria-selected=${vault.id === this.vaultId}
@click=${() => this._toggleVault(vault)}
>

View File

@ -4,7 +4,7 @@ import { customElement, property, query, state } from "lit/decorators.js";
import { shared } from "../styles";
@customElement("pl-select")
export class Select<T> extends LitElement {
export class Select<T = any> extends LitElement {
@property({ attribute: false })
options: { value: T; label?: string | TemplateResult | (() => string | TemplateResult); disabled?: boolean }[] = [];

View File

@ -1,3 +1,5 @@
import "./drawer";
import "./randomart";
import "./button";
import "./scroller";
import { css, html, LitElement } from "lit";
@ -117,31 +119,37 @@ export class SettingsAccount extends Routing(StateMixin(LitElement)) {
</header>
<pl-scroller class="stretch">
<div class="padded spacing vertical layout">
<h2 class="margined section-header">${$l("Profile")}</h2>
<div class="padded">
<div class="margined box">
<h2 class="padded uppercase bg-dark border-bottom semibold">${$l("Profile")}</h2>
<div class="padded start-aligning spacing horizontal layout">
<div class="vertical layout" style="width: 6.5em;" hidden>
<pl-fingerprint class="giant" .key=${app.account.publicKey}></pl-fingerprint>
<div>
<div class="list-item">
<pl-input
.label=${$l("Email")}
.value=${app.account.email}
disabled
class="transparent"
></pl-input>
</div>
<div class="list-item">
<pl-input
id="nameInput"
.label=${$l("Display Name")}
.value=${app.account.name}
@input=${() => this.requestUpdate()}
class="transparent"
></pl-input>
</div>
</div>
<div class="stretch spacing vertical layout">
<pl-input .label=${$l("Email")} .value=${app.account.email} disabled></pl-input>
<pl-input
id="nameInput"
.label=${$l("Display Name")}
.value=${app.account.name}
@input=${() => this.requestUpdate()}
></pl-input>
<pl-drawer .collapsed=${!this.hasChanges}>
<div class="horizontal spacing evenly stretching layout">
<pl-button class="primary" @click=${this._updateName}>${$l("Save")}</pl-button>
<pl-button @click=${this._resetName}>${$l("Cancel")}</pl-button>
</div>
</pl-drawer>
</div>
<pl-drawer .collapsed=${!this.hasChanges}>
<div class="horizontal padded spacing evenly stretching layout border-top">
<pl-button class="primary" @click=${this._updateName}>${$l("Save")}</pl-button>
<pl-button @click=${this._resetName}>${$l("Cancel")}</pl-button>
</div>
</pl-drawer>
</div>
<div class="horizontal padded spacing evenly stretching layout">

View File

@ -1,3 +1,4 @@
import "./button";
import "./scroller";
import { html, LitElement } from "lit";
import { StateMixin } from "../mixins/state";
@ -35,10 +36,11 @@ export class SettingsDisplay extends StateMixin(LitElement) {
</header>
<pl-scroller class="stretch">
<div class="double-padded spacing vertical layout">
<h2 class="margined section-header">${$l("Theme")}</h2>
<div class="double-margined box">
<h2 class="padded uppercase bg-dark border-bottom semibold">${$l("Theme")}</h2>
<pl-select
class="transparent"
.options=${[{ value: "auto" }, { value: "light" }, { value: "dark" }]}
id="themeSelect"
.value=${this.state.settings.theme as any}

View File

@ -1,3 +1,4 @@
import "./drawer";
import "./popover";
import "./list";
import "./button";
@ -338,83 +339,94 @@ export class SettingsSecurity extends StateMixin(Routing(LitElement)) {
const authenticators = this._getLoginAuthenticators();
return html`
<h2 class="margined section-header top-margined top-padded">${$l("Multi-Factor Authentication")}</h2>
<pl-list>
${authenticators.map(
(a, i) => html`
<div class="padded list-item box center-aligning horizontal layout">
<pl-icon
icon="${a.type === AuthType.Email ? "mail" : a.type === AuthType.Totp ? "time" : "usb"}"
class="large"
></pl-icon>
<div class="stretch collapse horizontally-padded left-margined">
<div class="ellipsis">${a.description}</div>
<div class="tiny wrapping tags top-margined">
${a.status === AuthenticatorStatus.Registering
? html`<div class="tag warning">${$l("not activated")}</div>`
: a.status === AuthenticatorStatus.Revoked
? html`<div class="tag warning">${$l("revoked")}</div>`
: html`
<div
class="tag"
title="Last Used: ${a.lastUsed
? formatDate(a.lastUsed)
: $l("never")}"
>
<pl-icon icon="time" class="inline"></pl-icon> ${a.lastUsed
? until(formatDateFromNow(a.lastUsed), "")
: $l("never")}
</div>
`}
<div class="box">
<h2 class="padded uppercase bg-dark border-bottom semibold">${$l("Multi-Factor Authentication")}</h2>
<pl-list>
${authenticators.map(
(a, i) => html`
<div class="padded list-item center-aligning horizontal layout">
<pl-icon
icon="${a.type === AuthType.Email
? "mail"
: a.type === AuthType.Totp
? "time"
: "usb"}"
class="large"
></pl-icon>
<div class="stretch collapse horizontally-padded left-margined">
<div class="ellipsis">${a.description}</div>
<div class="tiny wrapping tags top-margined">
${a.status === AuthenticatorStatus.Registering
? html`<div class="tag warning">${$l("not activated")}</div>`
: a.status === AuthenticatorStatus.Revoked
? html`<div class="tag warning">${$l("revoked")}</div>`
: html`
<div
class="tag"
title="Last Used: ${a.lastUsed
? formatDate(a.lastUsed)
: $l("never")}"
>
<pl-icon icon="time" class="inline"></pl-icon> ${a.lastUsed
? until(formatDateFromNow(a.lastUsed), "")
: $l("never")}
</div>
`}
</div>
</div>
<pl-button class="slim transparent reveal-on-parent-hover">
<pl-icon icon="more"></pl-icon>
</pl-button>
<pl-popover class="padded" hide-on-click>
<pl-list>
<div
class="padded horizontal spacing center-aligning layout list-item hover click"
@click=${() => this._testMFAuthenticator(a)}
>
<pl-icon icon="test"></pl-icon>
<div>${$l("Test")}</div>
</div>
<div
class="padded horizontal spacing center-aligning layout list-item hover click"
@click=${() => this._deleteAuthenticator(a)}
>
<pl-icon icon="delete"></pl-icon>
<div>${$l("Remove")}</div>
</div>
</pl-list>
</pl-popover>
<div
class="vertical layout reveal-on-parent-hover"
?hidden=${authenticators.length < 2}
>
<pl-button
class="transparent"
style="display: flex; --button-padding: 0 0.3em;"
?disabled=${i === 0}
@click=${() => this._moveAuthenticator(a, "up")}
>
<pl-icon icon="dropup"></pl-icon>
</pl-button>
<pl-button
class="transparent"
style="display: flex; --button-padding: 0 0.3em;"
?disabled=${i === authenticators.length - 1}
@click=${() => this._moveAuthenticator(a, "down")}
>
<pl-icon icon="dropdown"></pl-icon>
</pl-button>
</div>
</div>
<pl-button class="slim transparent reveal-on-parent-hover">
<pl-icon icon="more"></pl-icon>
</pl-button>
<pl-popover class="padded" hide-on-click>
<pl-list>
<div
class="padded horizontal spacing center-aligning layout list-item hover click"
@click=${() => this._testMFAuthenticator(a)}
>
<pl-icon icon="test"></pl-icon>
<div>${$l("Test")}</div>
</div>
<div
class="padded horizontal spacing center-aligning layout list-item hover click"
@click=${() => this._deleteAuthenticator(a)}
>
<pl-icon icon="delete"></pl-icon>
<div>${$l("Remove")}</div>
</div>
</pl-list>
</pl-popover>
<div class="vertical layout reveal-on-parent-hover" ?hidden=${authenticators.length < 2}>
<pl-button
class="transparent"
style="display: flex; --button-padding: 0 0.3em;"
?disabled=${i === 0}
@click=${() => this._moveAuthenticator(a, "up")}
>
<pl-icon icon="dropup"></pl-icon>
</pl-button>
<pl-button
class="transparent"
style="display: flex; --button-padding: 0 0.3em;"
?disabled=${i === authenticators.length - 1}
@click=${() => this._moveAuthenticator(a, "down")}
>
<pl-icon icon="dropdown"></pl-icon>
</pl-button>
</div>
</div>
`
)}
</pl-list>
<pl-button id="addMFAButton" class="small ghost" @click=${this._addAuthenticator}>
<pl-icon icon="add" class="right-margined"></pl-icon>
<div>${$l("Add MFA Method")}</div>
</pl-button>
`
)}
</pl-list>
<div class="list-item">
<pl-button id="addMFAButton" class="transparent" @click=${this._addAuthenticator}>
<pl-icon icon="add" class="right-margined"></pl-icon>
<div>${$l("Add MFA Method")}</div>
</pl-button>
</div>
</div>
`;
}
@ -425,52 +437,54 @@ export class SettingsSecurity extends StateMixin(Routing(LitElement)) {
const { sessions } = app.authInfo;
sessions.sort((a, b) => Number(b.lastUsed) - Number(a.lastUsed));
return html`
<h2 class="margined section-header top-margined top-padded">${$l("Active Sessions")}</h2>
<pl-list>
${sessions.map((session) => {
const lastKnownLocation = !session.lastLocation
? $l("Unknown")
: `${session.lastLocation.city || $l("Unknown City")}, ${
session.lastLocation.country || $l("Unknown Country")
}`;
return html`
<div class="padded list-item box center-aligning horizontal layout">
<pl-icon
icon="${["ios", "android"].includes(session.device?.platform.toLowerCase() || "")
? "mobile"
: "desktop"}"
class="large"
></pl-icon>
<div class="stretch collapse horizontally-padded left-margined">
<div class="ellipsis">${session.device?.description || $l("Unknown Device")}</div>
<div class="tiny tags top-margined">
${session.id === app.session!.id
? html` <div class="tag highlight">
<strong>${$l("Current Session")}</strong>
</div>`
: ""}
<div class="tag" title="Last Active: ${formatDate(session.lastUsed)}">
<pl-icon icon="time" class="inline"></pl-icon> ${session.lastUsed
? until(formatDateFromNow(session.lastUsed), "")
: $l("never")}
</div>
<div class="box">
<h2 class="padded uppercase bg-dark border-bottom semibold">${$l("Active Sessions")}</h2>
<pl-list>
${sessions.map((session) => {
const lastKnownLocation = !session.lastLocation
? $l("Unknown")
: `${session.lastLocation.city || $l("Unknown City")}, ${
session.lastLocation.country || $l("Unknown Country")
}`;
return html`
<div class="padded list-item center-aligning horizontal layout">
<pl-icon
icon="${["ios", "android"].includes(session.device?.platform.toLowerCase() || "")
? "mobile"
: "desktop"}"
class="large"
></pl-icon>
<div class="stretch collapse horizontally-padded left-margined">
<div class="ellipsis">${session.device?.description || $l("Unknown Device")}</div>
<div class="tiny tags top-margined">
${session.id === app.session!.id
? html` <div class="tag highlight">
<strong>${$l("Current Session")}</strong>
</div>`
: ""}
<div class="tag" title="Last Active: ${formatDate(session.lastUsed)}">
<pl-icon icon="time" class="inline"></pl-icon> ${session.lastUsed
? until(formatDateFromNow(session.lastUsed), "")
: $l("never")}
</div>
<div class="tag" title="Last Known Location: ${formatDate(session.lastUsed)}">
<pl-icon icon="location" class="inline"></pl-icon> ${lastKnownLocation}
<div class="tag" title="Last Known Location: ${formatDate(session.lastUsed)}">
<pl-icon icon="location" class="inline"></pl-icon> ${lastKnownLocation}
</div>
</div>
</div>
<pl-button
class="slim transparent reveal-on-parent-hover"
@click=${() => this._revokeSession(session)}
?disabled=${session.id === app.session!.id}
>
<pl-icon icon="delete"></pl-icon>
</pl-button>
</div>
<pl-button
class="slim transparent reveal-on-parent-hover"
@click=${() => this._revokeSession(session)}
?disabled=${session.id === app.session!.id}
>
<pl-icon icon="delete"></pl-icon>
</pl-button>
</div>
`;
})}
</pl-list>
`;
})}
</pl-list>
</div>
`;
}
@ -480,59 +494,65 @@ export class SettingsSecurity extends StateMixin(Routing(LitElement)) {
}
const { trustedDevices, sessions } = app.authInfo;
return html`
<h2 class="margined section-header top-margined top-padded">${$l("Trusted Devices")}</h2>
<pl-list>
${trustedDevices.map((device) => {
const latestSession = sessions
.filter((s) => s.device?.id === device.id)
.sort((a, b) => Number(b.lastUsed) - Number(a.lastUsed))[0];
const lastKnownLocation = !latestSession?.lastLocation
? $l("Unknown")
: `${latestSession.lastLocation.city || $l("Unknown City")}, ${
latestSession.lastLocation.country || $l("Unknown Country")
}`;
return html`
<div class="padded list-item box center-aligning horizontal layout">
<pl-icon
icon="${["ios", "android"].includes(device.platform.toLowerCase() || "")
? "mobile"
: "desktop"}"
class="large"
></pl-icon>
<div class="stretch collapse horizontally-padded left-margined">
<div class="ellipsis">${device.description || $l("Unknown Device")}</div>
<div class="tiny wrapping tags top-margined">
${device.id === app.state.device.id
? html` <div class="tag highlight">
<strong>${$l("Current Device")}</strong>
</div>`
: ""}
${latestSession
? html`
<div class="tag" title="Last Login: ${formatDate(latestSession.created)}">
<pl-icon icon="time" class="inline"></pl-icon> ${latestSession.created
? until(formatDateFromNow(latestSession.created), "")
: $l("never")}
</div>
<div class="box">
<h2 class="padded uppercase bg-dark border-bottom semibold">${$l("Trusted Devices")}</h2>
<pl-list>
${trustedDevices.map((device) => {
const latestSession = sessions
.filter((s) => s.device?.id === device.id)
.sort((a, b) => Number(b.lastUsed) - Number(a.lastUsed))[0];
const lastKnownLocation = !latestSession?.lastLocation
? $l("Unknown")
: `${latestSession.lastLocation.city || $l("Unknown City")}, ${
latestSession.lastLocation.country || $l("Unknown Country")
}`;
return html`
<div class="padded list-item center-aligning horizontal layout">
<pl-icon
icon="${["ios", "android"].includes(device.platform.toLowerCase() || "")
? "mobile"
: "desktop"}"
class="large"
></pl-icon>
<div class="stretch collapse horizontally-padded left-margined">
<div class="ellipsis">${device.description || $l("Unknown Device")}</div>
<div class="tiny wrapping tags top-margined">
${device.id === app.state.device.id
? html` <div class="tag highlight">
<strong>${$l("Current Device")}</strong>
</div>`
: ""}
${latestSession
? html`
<div
class="tag"
title="Last Login: ${formatDate(latestSession.created)}"
>
<pl-icon icon="time" class="inline"></pl-icon>
${latestSession.created
? until(formatDateFromNow(latestSession.created), "")
: $l("never")}
</div>
<div class="tag" title="Last Known Location: ${lastKnownLocation}">
<pl-icon icon="location" class="inline"></pl-icon>
${lastKnownLocation}
</div>
`
: ""}
<div class="tag" title="Last Known Location: ${lastKnownLocation}">
<pl-icon icon="location" class="inline"></pl-icon>
${lastKnownLocation}
</div>
`
: ""}
</div>
</div>
<pl-button
class="slim transparent reveal-on-parent-hover"
@click=${() => this._removeTrustedDevice(device)}
>
<pl-icon icon="delete"></pl-icon>
</pl-button>
</div>
<pl-button
class="slim transparent reveal-on-parent-hover"
@click=${() => this._removeTrustedDevice(device)}
>
<pl-icon icon="delete"></pl-icon>
</pl-button>
</div>
`;
})}
</pl-list>
`;
})}
</pl-list>
</div>
`;
}
@ -542,7 +562,7 @@ export class SettingsSecurity extends StateMixin(Routing(LitElement)) {
) {
const supportsPlatformAuth = await supportsPlatformAuthenticator();
return html`
<div class="padded list-item box center-aligning horizontal layout" ?disabled=${!supportsPlatformAuth}>
<div class="padded list-item center-aligning horizontal layout" ?disabled=${!supportsPlatformAuth}>
<pl-icon
icon="${["ios", "android"].includes(currentDevice.platform.toLowerCase() || "")
? "mobile"
@ -588,59 +608,61 @@ export class SettingsSecurity extends StateMixin(Routing(LitElement)) {
const currentDevice = app.state.device;
const currentAuthenticator = authenticators.find((a) => a.device?.id === currentDevice.id);
return html`
<h2 class="margined section-header top-margined top-padded">${$l("Biometric Unlock")}</h2>
<div class="box">
<h2 class="padded uppercase bg-dark border-bottom semibold">${$l("Biometric Unlock")}</h2>
<pl-list>
${until(this._renderBiometricUnlockCurrentDevice(currentDevice, currentAuthenticator), "")}
${keyStoreEntries.map((entry) => {
const authenticator = authenticators.find((a) => a.id === entry.authenticatorId);
const device = authenticator?.device;
if (device?.id === app.state.device.id) {
return;
}
return html`
<div class="padded list-item box center-aligning horizontal layout">
<pl-icon
icon="${["ios", "android"].includes(device?.platform.toLowerCase() || "")
? "mobile"
: "desktop"}"
class="large"
></pl-icon>
<div class="stretch collapse horizontally-padded left-margined">
<div class="ellipsis">${device?.description || $l("Unknown Device")}</div>
<div class="tiny wrapping tags top-margined">
${authenticator
? html`
<div
class="tag"
title="Last Used: ${authenticator.lastUsed
? formatDate(authenticator.lastUsed)
: $l("never")}"
>
<pl-icon icon="time" class="inline"></pl-icon>
${authenticator.lastUsed
? until(formatDateFromNow(authenticator.lastUsed), "")
: $l("never")}
</div>
`
: ""}
<pl-list>
${until(this._renderBiometricUnlockCurrentDevice(currentDevice, currentAuthenticator), "")}
${keyStoreEntries.map((entry) => {
const authenticator = authenticators.find((a) => a.id === entry.authenticatorId);
const device = authenticator?.device;
if (device?.id === app.state.device.id) {
return;
}
return html`
<div class="padded list-item box center-aligning horizontal layout">
<pl-icon
icon="${["ios", "android"].includes(device?.platform.toLowerCase() || "")
? "mobile"
: "desktop"}"
class="large"
></pl-icon>
<div class="stretch collapse horizontally-padded left-margined">
<div class="ellipsis">${device?.description || $l("Unknown Device")}</div>
<div class="tiny wrapping tags top-margined">
${authenticator
? html`
<div
class="tag"
title="Last Used: ${authenticator.lastUsed
? formatDate(authenticator.lastUsed)
: $l("never")}"
>
<pl-icon icon="time" class="inline"></pl-icon>
${authenticator.lastUsed
? until(formatDateFromNow(authenticator.lastUsed), "")
: $l("never")}
</div>
`
: ""}
</div>
</div>
</div>
<pl-toggle
.active=${true}
class="click"
@change=${(e: Event) => this._revokeBiometricUnlock(entry, device, e)}
></pl-toggle>
<!-- <pl-button
<pl-toggle
.active=${true}
class="click"
@change=${(e: Event) => this._revokeBiometricUnlock(entry, device, e)}
></pl-toggle>
<!-- <pl-button
class="slim transparent reveal-on-parent-hover"
@click=${() => this._revokeBiometricUnlock(entry)}
>
<pl-icon icon="delete"></pl-icon>
</pl-button> -->
</div>
`;
})}
</pl-list>
</div>
`;
})}
</pl-list>
</div>
`;
}
@ -656,33 +678,45 @@ export class SettingsSecurity extends StateMixin(Routing(LitElement)) {
</header>
<pl-scroller class="stretch">
<div class="wrapper double-padded spacing vertical layout">
<h2 class="margined section-header">${$l("Master Password")}</h2>
<div class="wrapper double-padded double-spacing vertical layout">
<div class="box">
<h2 class="padded uppercase bg-dark border-bottom semibold">${$l("Master Password")}</h2>
<pl-button @click=${() => this._changePassword()}> ${$l("Change Master Password")} </pl-button>
<pl-button class="transparent" @click=${() => this._changePassword()}>
${$l("Change Master Password")}
</pl-button>
</div>
<h2 class="margined section-header top-margined top-padded">${$l("Auto Lock")}</h2>
<div class="box">
<h2 class="padded uppercase bg-dark border-bottom semibold">${$l("Auto Lock")}</h2>
<pl-toggle-button
id="autoLockButton"
.active=${app.settings.autoLock}
.label=${$l("Lock Automatically")}
reverse
>
</pl-toggle-button>
<div>
<pl-toggle-button
class="transparent"
id="autoLockButton"
.active=${app.settings.autoLock}
.label=${$l("Lock Automatically")}
reverse
>
</pl-toggle-button>
</div>
<pl-slider
id="autoLockDelaySlider"
min="1"
max="10"
step="1"
.value=${app.settings.autoLockDelay}
.unit=${$l(" min")}
.label=${$l("After")}
?hidden=${!app.settings.autoLock}
class="item"
>
</pl-slider>
<pl-drawer .collapsed=${!app.settings.autoLock}>
<div class="half-padded border-top">
<pl-slider
id="autoLockDelaySlider"
min="1"
max="10"
step="1"
.value=${app.settings.autoLockDelay}
.unit=${$l(" min")}
.label=${$l("After")}
class="item"
>
</pl-slider>
</div>
</pl-drawer>
</div>
${this._renderBiometricUnlock()} ${this._renderMFA()} ${this._renderSessions()}
${this._renderTrustedDevices()}

View File

@ -49,18 +49,22 @@ export class SettingsTools extends StateMixin(LitElement) {
</header>
<pl-scroller class="stretch">
<div class="padded spacing vertical layout">
<h2 class="margined section-header">${$l("Import / Export")}</h2>
<div class="double-margined box">
<h2 class="padded uppercase bg-dark semibold">${$l("Import / Export")}</h2>
<pl-button @click=${() => this._import()}
><pl-icon icon="import" class="right-margined"></pl-icon>
<div>${$l("Import...")}</div></pl-button
>
<div class="list-item">
<pl-button class="transparent" @click=${() => this._import()}>
<pl-icon icon="import" class="right-margined"></pl-icon>
<div>${$l("Import...")}</div>
</pl-button>
</div>
<pl-button @click=${() => this._export()}
><pl-icon icon="export" class="right-margined"></pl-icon>
<div>${$l("Export...")}</div></pl-button
>
<div class="list-item">
<pl-button class="transparent" @click=${() => this._export()}>
<pl-icon icon="export" class="right-margined"></pl-icon>
<div>${$l("Export...")}</div>
</pl-button>
</div>
</div>
</pl-scroller>
</div>

View File

@ -76,11 +76,11 @@ export class Settings extends StateMixin(Routing(View)) {
</pl-button>
</header>
<pl-scroller class="stretch">
<nav class="margined">
<nav>
<pl-list>
<div
role="link"
class="menu-item"
class="double-padded center-aligning spacing horizontal layout list-item click hover"
aria-selected=${this._page === "account"}
@click=${() => this.go("settings/account")}
>
@ -89,7 +89,7 @@ export class Settings extends StateMixin(Routing(View)) {
</div>
<div
role="link"
class="menu-item"
class="double-padded center-aligning spacing horizontal layout list-item click hover"
aria-selected=${this._page === "security"}
@click=${() => this.go("settings/security")}
>
@ -98,7 +98,7 @@ export class Settings extends StateMixin(Routing(View)) {
</div>
<div
role="link"
class="menu-item"
class="double-padded center-aligning spacing horizontal layout list-item click hover"
aria-selected=${this._page === "display"}
@click=${() => this.go("settings/display")}
>
@ -107,7 +107,7 @@ export class Settings extends StateMixin(Routing(View)) {
</div>
<div
role="link"
class="menu-item"
class="double-padded center-aligning spacing horizontal layout list-item click hover"
aria-selected=${this._page === "billing"}
@click=${() => this.go("settings/billing")}
hidden
@ -117,7 +117,7 @@ export class Settings extends StateMixin(Routing(View)) {
</div>
<div
role="link"
class="menu-item"
class="double-padded center-aligning spacing horizontal layout list-item click hover"
aria-selected=${this._page === "tools"}
@click=${() => this.go("settings/tools")}
>
@ -126,7 +126,7 @@ export class Settings extends StateMixin(Routing(View)) {
</div>
<div
role="link"
class="menu-item"
class="double-padded center-aligning spacing horizontal layout list-item click hover"
aria-selected=${this._page === "billing"}
@click=${() => this.go("settings/billing")}
>
@ -135,7 +135,7 @@ export class Settings extends StateMixin(Routing(View)) {
</div>
<div
role="link"
class="menu-item"
class="double-padded center-aligning spacing horizontal layout list-item click hover"
aria-selected=${this._page === "about"}
@click=${() => this.go("settings/about")}
hidden

View File

@ -25,6 +25,10 @@ export class ToggleButton extends LitElement {
display: inline-block;
pointer-events: none;
}
:host(.transparent) pl-button {
--button-ghost-border-color: transparent;
}
`,
];

View File

@ -1,5 +1,5 @@
import { VaultID } from "@padloc/core/src/vault";
import { css, html, LitElement } from "lit";
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { shared } from "../styles";
import "./icon";
@ -15,33 +15,17 @@ export class VaultItem extends LitElement {
@property({ type: Number })
members: number = 0;
static styles = [
shared,
css`
.icon {
font-size: 120%;
background: var(--color-shade-1);
border: solid 1px var(--border-color);
border-radius: 100%;
width: 2em;
height: 2em;
}
.tags {
margin-top: 0.2em;
}
`,
];
static styles = [shared];
render() {
return html`
<div class="horizontal spacing center-aligning layout">
<pl-icon class="icon" icon="vault"></pl-icon>
<pl-icon class="large" icon="vault"></pl-icon>
<div class="stretch">
<div class="bold ellipsis">${this.vault.name}</div>
<div class="small">
<div class="small top-half-margined">
<div class="tiny tags">
<div class="tag">
<pl-icon icon="group" class="inline"></pl-icon>

View File

@ -273,7 +273,7 @@ export class VaultView extends Routing(StateMixin(LitElement)) {
</pl-button>
<pl-input
class="transparent large bold skinny dashed stretch"
class="transparent large bold skinny stretch"
placeholder="Enter Vault Name"
id="nameInput"
@change=${() => this.requestUpdate()}
@ -296,10 +296,11 @@ export class VaultView extends Routing(StateMixin(LitElement)) {
</header>
<pl-scroller class="stretch">
<section class="double-margined">
<h2 class="center-aligning horizontal layout">
<div class="margined section-header stretch">${$l("Groups")}</div>
<pl-button class="slim transparent">
<section class="double-margined box">
<h2 class="center-aligning horizontal layout bg-dark border-bottom semibold">
<div class="padded uppercase stretch">${$l("Groups")}</div>
<pl-button class="skinny half-margined transparent">
<pl-icon icon="add"></pl-icon>
</pl-button>
@ -368,10 +369,10 @@ export class VaultView extends Routing(StateMixin(LitElement)) {
</pl-list>
</section>
<section class="double-margined">
<h2 class="center-aligning horizontal layout">
<div class="margined section-header stretch">${$l("Members")}</div>
<pl-button class="slim transparent">
<section class="double-margined box">
<h2 class="center-aligning horizontal layout bg-dark border-bottom semibold">
<div class="padded uppercase stretch">${$l("Members")}</div>
<pl-button class="skinny half-margined transparent">
<pl-icon icon="add"></pl-icon>
</pl-button>

View File

@ -175,6 +175,16 @@ export const layout = css`
margin-right: calc(2 * var(--spacing));
}
.vertically-half-margined {
margin-top: calc(0.5 * var(--spacing));
margin-bottom: calc(0.5 * var(--spacing));
}
.vertically-double-margined {
margin-top: calc(2 * var(--spacing));
margin-bottom: calc(2 * var(--spacing));
}
.bottom-margined {
margin-bottom: var(--spacing);
}

View File

@ -93,6 +93,22 @@ export const misc = css`
border-color: var(--box-border-color, var(--border-color));
border-style: var(--box-border-style, solid);
border-width: var(--box-border-width, 1px);
overflow: hidden;
}
.background,
.bg {
background: var(--color-background);
}
.background-dark,
.bg-dark {
background: var(--color-background-dark);
}
.uppercase {
font-variant: all-small-caps;
letter-spacing: 0.1em;
}
.font-mono {
@ -112,44 +128,24 @@ export const misc = css`
}
.list-item {
transition: transform 0.2s cubic-bezier(0.05, 0.7, 0.03, 3) 0s;
border-radius: 0.5em;
position: relative;
transition: box-shadow 0.2s;
}
.list-item:not(:first-child) {
border-top-style: solid;
border-top-width: 1px;
border-top-color: var(--list-item-border-color, var(--border-color));
}
.list-item.box:not(:first-child) {
margin-top: var(--spacing);
}
.list-item:not(.box):not(.hover)::before,
.list-item:not(.box).hover:not(:hover)::before {
content: "";
display: block;
content: "";
position: absolute;
top: 0;
left: 0.5em;
right: 0.5em;
width: auto;
height: 0;
overflow: hidden;
margin: 0 auto;
border-bottom-style: solid;
border-bottom-width: 1px;
border-bottom-color: var(--list-item-border-color, var(--border-color));
}
.list-item:not(.box).hover:hover + .list-item::before,
.list-item:not(.box):not([aria-posinset]):first-child::before,
.list-item:not(.box)[aria-posinset="0"]::before {
border-color: transparent !important;
}
.list-item[aria-selected="true"] {
background: var(--list-item-selected-background);
color: var(--list-item-selected-color);
--color-highlight: var(--list-item-selected-color-highlight);
transform: scale(1.02);
/* box-shadow: inset 0.2em 0 0 0 var(--color-highlight); */
}
.section-header {