Custom backgrounds for lists #144

Merged
konrad merged 27 commits from feature/custom-backgrounds into master 2020-05-31 19:17:29 +00:00
27 changed files with 513 additions and 70 deletions

View File

@ -3,13 +3,27 @@
<div v-if="online">
<!-- This is a workaround to get the sw to "see" the to-be-cached version of the offline background image -->
<div class="offline" style="height: 0;width: 0;"></div>
<nav class="navbar main-theme is-fixed-top" role="navigation" aria-label="main navigation"
v-if="userAuthenticated && (userInfo && userInfo.type === authTypes.USER)">
<nav
class="navbar main-theme is-fixed-top"
:class="{'has-background': background}"
role="navigation"
aria-label="main navigation"
v-if="userAuthenticated && (userInfo && userInfo.type === authTypes.USER)">
<div class="navbar-brand">
<router-link :to="{name: 'home'}" class="navbar-item logo">
<img src="/images/logo-full.svg" alt="Vikunja"/>
</router-link>
</div>
<div class="list-title" v-if="currentList.id">
<h1
class="title"
:style="{ 'opacity': currentList.title === '' ? '0': '1' }">
{{ currentList.title === '' ? 'Loading...': currentList.title}}
</h1>
<router-link :to="{ name: 'editList', params: { id: currentList.id } }" class="icon">
<icon icon="cog" size="2x"/>
</router-link>
</div>
<div class="navbar-end">
<div v-if="updateAvailable" class="update-notification">
<p>There is an update for Vikunja available!</p>
@ -49,7 +63,11 @@
<a @click="mobileMenuActive = false" class="mobilemenu-hide-button" v-if="mobileMenuActive">
<icon icon="times"></icon>
</a>
<div class="app-container">
<div
class="app-container"
:class="{'has-background': background}"
:style="{'background-image': `url(${background})`}"
>
<div class="namespace-container" :class="{'is-active': mobileMenuActive}">
<div class="menu top-menu">
<router-link :to="{name: 'home'}" class="logo">
@ -141,7 +159,7 @@
<div class="more-container" :key="n.id + 'child'">
<ul class="menu-list can-be-hidden" >
<li v-for="l in n.lists" :key="l.id">
<router-link :to="{ name: 'list.index', params: { listId: l.id} }" :class="{'router-link-exact-active': currentList === l.id}">
<router-link :to="{ name: 'list.index', params: { listId: l.id} }" :class="{'router-link-exact-active': currentList.id === l.id}">
<span class="name">
<span class="color-bubble" v-if="l.hexColor !== ''" :style="{ backgroundColor: l.hexColor }"></span>
{{l.title}}
@ -316,6 +334,7 @@
return state.namespaces.namespaces.filter(n => this.showArchived ? true : !n.isArchived)
},
currentList: CURRENT_LIST,
background: 'background',
}),
methods: {
logout() {
@ -351,7 +370,7 @@
this.$route.name === 'migrate.wunderlist' ||
this.$route.name === 'userSettings'
) {
this.$store.commit(CURRENT_LIST, 0)
this.$store.commit(CURRENT_LIST, {})
}
},
showRefreshUI(e) {

View File

@ -1,5 +1,5 @@
<template>
<div class="loader-container" :class="{ 'is-loading': listService.loading}">
<div class="loader-container edit-list" :class="{ 'is-loading': listService.loading}">
<div class="notification is-warning" v-if="list.isArchived">
This list is archived.
It is not possible to create new or edit tasks or it.
@ -104,6 +104,8 @@
</div>
</div>
<background :list-id="$route.params.id"/>
<component
:is="manageUsersComponent"
:id="list.id"
@ -141,6 +143,8 @@
import ListModel from '../../models/list'
import ListService from '../../services/list'
import Fancycheckbox from '../global/fancycheckbox'
import Background from './settings/background'
import {CURRENT_LIST} from '../../store/mutation-types'
export default {
name: "EditList",
@ -156,6 +160,7 @@
}
},
components: {
Background,
Fancycheckbox,
LinkSharing,
manageSharing,
@ -183,6 +188,7 @@
this.listService.get(list)
.then(r => {
this.$set(this, 'list', r)
this.$store.commit(CURRENT_LIST, r)
// This will trigger the dynamic loading of components once we actually have all the data to pass to them
this.manageTeamsComponent = 'manageSharing'
this.manageUsersComponent = 'manageSharing'

View File

@ -1,20 +1,33 @@
<template>
<div class="loader-container" :class="{ 'is-loading': listService.loading}">
<div class="content">
<router-link :to="{ name: 'editList', params: { id: list.id } }" class="icon settings is-medium">
<icon icon="cog" size="2x"/>
<div
class="loader-container"
:class="{ 'is-loading': listService.loading}"
>
<div class="switch-view">
<router-link
:to="{ name: 'list.list', params: { listId: listId } }"
:class="{'is-active': $route.name === 'list.list'}">
List
</router-link>
<h1 :style="{ 'opacity': list.title === '' ? '0': '1' }">{{ list.title === '' ? 'Loading...': list.title}}</h1>
<div class="notification is-warning" v-if="list.isArchived">
This list is archived.
It is not possible to create new or edit tasks or it.
</div>
<div class="switch-view">
<router-link :to="{ name: 'list.list', params: { listId: listId } }" :class="{'is-active': $route.name === 'list.list'}">List</router-link>
<router-link :to="{ name: 'list.gantt', params: { listId: listId } }" :class="{'is-active': $route.name === 'list.gantt'}">Gantt</router-link>
<router-link :to="{ name: 'list.table', params: { listId: listId } }" :class="{'is-active': $route.name === 'list.table'}">Table</router-link>
<router-link :to="{ name: 'list.kanban', params: { listId: listId } }" :class="{'is-active': $route.name === 'list.kanban'}">Kanban</router-link>
</div>
<router-link
:to="{ name: 'list.gantt', params: { listId: listId } }"
:class="{'is-active': $route.name === 'list.gantt'}">
Gantt
</router-link>
<router-link
:to="{ name: 'list.table', params: { listId: listId } }"
:class="{'is-active': $route.name === 'list.table'}">
Table
</router-link>
<router-link
:to="{ name: 'list.kanban', params: { listId: listId } }"
:class="{'is-active': $route.name === 'list.kanban'}">
Kanban
</router-link>
</div>
<div class="notification is-warning" v-if="list.isArchived">
This list is archived.
It is not possible to create new or edit tasks or it.
</div>
<router-view/>
@ -53,12 +66,15 @@
listId() {
return typeof this.$route.params.listId === 'undefined' ? 0 : this.$route.params.listId
},
background() {
return this.$store.state.background
},
},
methods: {
loadList() {
// Don't load the list if we either already loaded it or aren't dealing with a list at all currently
if(this.$route.params.listId === this.listLoaded || typeof this.$route.params.listId === 'undefined') {
if (this.$route.params.listId === this.listLoaded || typeof this.$route.params.listId === 'undefined') {
return
}
@ -76,13 +92,12 @@
return
}
this.$store.commit(CURRENT_LIST, Number(this.$route.params.listId))
// We create an extra list object instead of creating it in this.list because that would trigger a ui update which would result in bad ux.
let list = new ListModel({id: this.$route.params.listId})
this.listService.get(list)
.then(r => {
this.$set(this, 'list', r)
this.$store.commit(CURRENT_LIST, r)
})
.catch(e => {
this.error(e, this)

View File

@ -0,0 +1,132 @@
<template>
<div
v-if="unsplashBackgroundEnabled"
class="card list-background-setting loader-container"
:class="{ 'is-loading': backgroundService.loading}">
<header class="card-header">
<p class="card-header-title">
Set list background
</p>
</header>
<div class="card-content">
<div class="content">
<input
type="text"
placeholder="Search for a background..."
class="input is-expanded"
v-model="backgroundSearchTerm"
@keyup="() => newBackgroundSearch()"
:class="{'is-loading': backgroundService.loading}"
/>
<div class="image-search-result">
<a
@click="() => setBackground(im.id)"
class="image"
v-for="im in backgroundSearchResult"
:style="{'background-image': `url(${backgroundThumbs[im.id]})`}"
:key="im.id">
<a class="info" :href="`https://unsplash.com/@${im.info.author}`">
{{ im.info.authorName }}
</a>
</a>
</div>
<a
v-if="backgroundSearchResult.length > 0"
class="button is-primary is-centered is-load-more-button is-outlined noshadow"
@click="() => searchBackgrounds(currentPage + 1)"
:disabled="backgroundService.loading"
>
<template v-if="backgroundService.loading">
Loading...
</template>
<template v-else>
Load more photos
</template>
</a>
</div>
</div>
</div>
</template>
<script>
import BackgroundUnsplashService from '../../../services/backgroundUnsplash'
import {CURRENT_LIST} from '../../../store/mutation-types'
export default {
name: 'background',
data() {
return {
backgroundSearchTerm: '',
backgroundSearchResult: [],
backgroundService: null,
backgroundThumbs: {},
currentPage: 1,
backgroundSearchTimeout: null,
}
},
props: {
listId: {
default: 0,
required: true,
}
},
computed: {
unsplashBackgroundEnabled() {
return this.$store.state.config.enabledBackgroundProviders.includes('unsplash')
},
},
created() {
this.backgroundService = new BackgroundUnsplashService()
// Show the default collection of backgrounds
this.newBackgroundSearch()
},
methods: {
newBackgroundSearch() {
// This is an extra method to reset a few things when searching to not break loading more photos.
this.$set(this, 'backgroundSearchResult', [])
this.$set(this, 'backgroundThumbs', {})
this.searchBackgrounds()
},
searchBackgrounds(page = 1) {
if(this.backgroundSearchTimeout !== null) {
clearTimeout(this.backgroundSearchTimeout)
}
// We're using the timeout to not search on every keypress but with a 300ms delay.
// If another key is pressed within these 300ms, the last search request is dropped and a new one is scheduled.
this.backgroundSearchTimeout = setTimeout(() => {
this.currentPage = page
this.backgroundService.getAll({}, {s: this.backgroundSearchTerm, p: page})
.then(r => {
this.backgroundSearchResult = this.backgroundSearchResult.concat(r)
r.forEach(b => {
this.backgroundService.thumb(b)
.then(t => {
this.$set(this.backgroundThumbs, b.id, t)
})
})
})
.catch(e => {
this.error(e, this)
})
}, 300)
},
setBackground(backgroundId) {
// Don't set a background if we're in the process of setting one
if (this.backgroundService.loading) {
return
}
this.backgroundService.update({id: backgroundId, listId: this.listId})
.then(l => {
this.$store.commit(CURRENT_LIST, l)
this.success({message: 'The background has been set successfully!'}, this)
})
.catch(e => {
this.error(e, this)
})
},
},
}
</script>

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="gantt-chart-container">
<div class="gantt-options">
<fancycheckbox v-model="showTaskswithoutDates" class="is-block">
Show tasks which don't have dates set

View File

@ -1,5 +1,5 @@
<template>
<div class="loader-container" :class="{ 'is-loading': taskService.loading}">
<div class="loader-container task-view-container" :class="{ 'is-loading': taskService.loading}">
<div class="task-view">
<div class="heading">
<h1 class="title task-id" v-if="task.identifier === ''">

View File

@ -22,8 +22,6 @@ export const getListView = listId => {
localStorage.removeItem('listView')
}
console.log('saved list view state', savedListView)
if (!savedListView) {
return 'list.list'
}

View File

@ -0,0 +1,12 @@
import AbstractModel from './abstractModel'
export default class BackgroundImageModel extends AbstractModel {
defaults() {
return {
id: 0,
url: '',
thumb: '',
info: {},
}
}
}

View File

@ -34,6 +34,7 @@ export default class ListModel extends AbstractModel {
isArchived: false,
hexColor: '',
identifier: '',
backgroundInformation: null,
created: null,
updated: null,

View File

@ -0,0 +1,34 @@
import AbstractService from './abstractService'
import BackgroundImageModel from '../models/backgroundImage'
import ListModel from '../models/list'
export default class BackgroundUnsplashService extends AbstractService {
constructor() {
super({
getAll: '/backgrounds/unsplash/search',
update: '/lists/{listId}/backgrounds/unsplash',
})
}
modelFactory(data) {
return new BackgroundImageModel(data)
}
modelUpdateFactory(data) {
return new ListModel(data)
}
thumb(model) {
return this.http({
url: `/backgrounds/unsplash/images/${model.url}/thumb`,
method: 'GET',
responseType: 'blob',
})
.then(response => {
return window.URL.createObjectURL(new Blob([response.data]))
})
.catch(e => {
return e
})
}
}

View File

@ -37,4 +37,22 @@ export default class ListService extends AbstractService {
list.hexColor = list.hexColor.substring(1, 7)
return list
}
background(list) {
if (list.background === null) {
return Promise.resolve('')
}
return this.http({
url: `/lists/${list.id}/background`,
method: 'GET',
responseType: 'blob',
})
.then(response => {
return window.URL.createObjectURL(new Blob([response.data]))
})
.catch(e => {
return e
})
}
}

View File

@ -1,5 +1,6 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import {CURRENT_LIST, ERROR_MESSAGE, IS_FULLPAGE, LOADING, ONLINE} from './mutation-types'
@ -9,6 +10,7 @@ import namespaces from './modules/namespaces'
import kanban from './modules/kanban'
import tasks from './modules/tasks'
import lists from './modules/lists'
import ListService from '../services/list'
export const store = new Vuex.Store({
modules: {
@ -25,7 +27,8 @@ export const store = new Vuex.Store({
online: true,
isFullpage: false,
// This is used to highlight the current list in menu for all list related views
currentList: 0,
currentList: {id: 0},
background: '',
},
mutations: {
[LOADING](state, loading) {
@ -41,6 +44,29 @@ export const store = new Vuex.Store({
state.isFullpage = fullpage
},
[CURRENT_LIST](state, currentList) {
// Not sure if this is the right way to do it but hey, it works
if (
currentList.id !== state.currentList.id ||
(
currentList.backgroundInformation &&
currentList.backgroundInformation.unsplashId &&
currentList.backgroundInformation.unsplashId !== state.currentList.backgroundInformation.unsplashId
)
) {
if (currentList.backgroundInformation) {
const listService = new ListService()
listService.background(currentList)
.then(b => {
state.background = b
})
.catch(e => {
console.error('Error getting background image for list', currentList.id, e)
})
} else {
state.background = null
}
}
state.currentList = currentList
},
},

View File

@ -14,6 +14,7 @@ export default {
availableMigrators: [],
taskAttachmentsEnabled: true,
totpEnabled: true,
enabledBackgroundProviders: [],
}),
mutations: {
[CONFIG](state, config) {
@ -26,6 +27,7 @@ export default {
state.availableMigrators = config.available_migrators
state.taskAttachmentsEnabled = config.task_attachments_enabled
state.totpEnabled = config.totp_enabled
state.enabledBackgroundProviders = config.enabled_background_providers
},
},
actions: {

View File

@ -16,3 +16,4 @@
@import 'table-view';
@import 'kanban';
@import 'modal';
@import 'list-backgrounds';

View File

@ -1,6 +1,10 @@
$gantt-border: 1px solid $grey-lighter;
$gantt-vertical-border-color: lighten($grey, 45);
.gantt-chart-container {
padding-bottom: 1em;
}
.gantt-chart {
padding: 5px 0;
overflow-x: auto;

View File

@ -2,7 +2,7 @@ $bucket-background: #e8f0f5;
$task-background: $white;
$ease-out: all .3s cubic-bezier(0.23, 1, 0.32, 1);
$crazy-height-calculation: '100vh - 4.5rem - 1.5rem - 116px - 1em - 1.5em';
$crazy-height-calculation: '100vh - 4.5rem - 1.5rem - 1em - 1.5em + 4px';
.kanban {
@ -26,7 +26,7 @@ $crazy-height-calculation: '100vh - 4.5rem - 1.5rem - 116px - 1em - 1.5em';
min-height: 20px;
.tasks {
max-height: calc(#{$crazy-height-calculation} - 1rem - 2.5rem - 1em - #{$button-height} - 1em);
max-height: calc(#{$crazy-height-calculation} - 1rem - 2.5rem - 2em - #{$button-height} - 1em);
overflow: auto;
.task {

View File

@ -0,0 +1,75 @@
.list-background-setting {
.image-search-result {
margin-top: 1em;
display: flex;
flex-flow: row wrap;
.image {
width: calc(100% / 6 - 1em);
height: 120px;
margin: .5em;
background-size: cover;
background-position: center;
display: flex;
@media screen and (min-width: $desktop) {
&:nth-child(6n) {
page-break-after: always; // CSS 2.1 syntax
break-after: always; // New syntax
}
}
@media screen and (max-width: $desktop) {
width: calc(100% / 4 - 1em);
&:nth-child(4n) {
page-break-after: always; // CSS 2.1 syntax
break-after: always; // New syntax
}
}
@media screen and (max-width: $tablet) {
width: calc(100% / 2 - 1em);
&:nth-child(2n) {
page-break-after: always; // CSS 2.1 syntax
break-after: always; // New syntax
}
}
@media screen and (max-width: ($tablet / 2)) {
width: calc(100% - 1em);
&:nth-child(1n) {
page-break-after: always; // CSS 2.1 syntax
break-after: always; // New syntax
}
}
.info {
align-self: flex-end;
display: block;
opacity: 0;
width: 100%;
padding: .25em 0;
text-align: center;
background: rgba(0, 0, 0, 0.5);
font-size: .75em;
font-weight: bold;
color: $white;
transition: opacity $transition;
}
&:hover .info {
opacity: 1;
}
}
}
.is-load-more-button {
margin: 0 auto;
display: block;
width: 200px;
}
}

View File

@ -1,4 +1,4 @@
.card.is-fullwidth{
.card.is-fullwidth {
margin-bottom: 1rem;
.add-form {
@ -9,26 +9,26 @@
padding: 0;
}
.table{
.table {
border-top: 1px solid darken(#fff, 15%);
border-radius: 4px;
overflow: hidden;
td{
td {
vertical-align: middle;
}
td.type, td.actions{
td.type, td.actions {
width: 250px;
}
td.actions{
td.actions {
text-align: right;
}
}
}
.sharables-list, .sharables-namespace{
.sharables-list, .sharables-namespace {
padding: 0 !important;
}
@ -39,12 +39,16 @@
.search {
max-width: 300px;
margin-top: -78px;
margin-top: -58px;
float: right;
display: flex;
align-items: center;
justify-content: space-between;
.button, .input {
height: $switch-view-height;
}
.field {
transition: width $transition;
width: 100%;
@ -61,3 +65,23 @@
}
}
}
.list-title {
display: flex;
align-items: center;
h1 {
margin: 0;
}
.icon {
color: $grey-dark;
margin-left: 1rem;
height: 1rem;
width: 1rem;
}
}
.edit-list {
padding-bottom: 1em;
}

View File

@ -1,10 +1,11 @@
.switch-view {
background: $white;
display: inline-block;
margin: 1em 0;
border-radius: $radius;
font-size: .8em;
box-shadow: 0.3em 0.3em 0.8em darken($light, 6);
height: $switch-view-height;
margin-bottom: 1em;
a {
padding: .5em;

View File

@ -1,8 +1,8 @@
.table-view {
overflow-x: scroll;
.table {
background: transparent;
overflow-x: auto;
overflow-y: hidden;
.user {
margin: 0;
@ -19,8 +19,12 @@
width: 100%;
max-width: 180px;
position: absolute;
right: 3em;
margin-top: -80px;
right: 1.5em;
margin-top: -58px;
.button {
height: $switch-view-height;
}
.card {
text-align: left;

View File

@ -1,7 +1,7 @@
.task-view {
// This is a workaround to hide the llama background from the top on the task detail page
margin-top: -1.5em;
padding-top: 1.5em;
padding: 1em;
background-color: $light-background;
.subtitle {
@ -165,6 +165,10 @@
}
}
.task-view-container {
padding-bottom: 1em;
}
.is-done {
background: $green;
color: $white;

View File

@ -1,8 +1,3 @@
.settings{
float: right;
color: rgb(74, 74, 74);
}
.tasks {
margin-top: 1rem;
padding: 0;

View File

@ -9,3 +9,4 @@
@import 'notification';
@import 'offline';
@import 'update-notification';
@import 'background';

View File

@ -0,0 +1,50 @@
.app-container.has-background {
background-position: center;
background-size: cover;
background-repeat: no-repeat;
background-attachment: fixed;
min-height: 100vh;
.namespace-container {
background: $transparent-background-light;
}
.tasks {
background: $transparent-background-light;
border-radius: $radius;
.task:first-child {
border-radius: $radius $radius 0 0;
}
.task:last-child {
border-radius: 0 0 $radius $radius;
}
}
.table-view .table {
background: $transparent-background-light;
border-radius: $radius;
}
.pagination-link:not(.is-current) {
background: $light-background;
}
.box,
.card,
.switch-view,
.table-view .button,
.search .button {
box-shadow: none;
}
.task-view {
border-radius: $radius;
margin-top: 1em;
}
}
.navbar.has-background {
background: $transparent-background-light;
}

View File

@ -9,6 +9,7 @@
border-color: darken($color, 5);
}
}
.navbar-dropdown {
box-shadow: $navbar-dropdown-boxed-shadow;
top: 101%;
@ -18,6 +19,17 @@
.navbar.main-theme {
background: $light-background;
z-index: 5 !important;
justify-content: space-between;
align-items: center;
.title {
margin: 0;
font-size: 1.75rem;
}
.navbar-end {
margin-left: 0;
}
@media screen and (max-width: $desktop) {
display: flex;
@ -48,6 +60,7 @@
border: none;
box-shadow: none;
}
@each $name, $pair in $colors {
$color: nth($pair, 1);
$color-invert: nth($pair, 2);
@ -65,16 +78,16 @@
}
}
.navbar-menu .navbar-item .icon{
.navbar-menu .navbar-item .icon {
margin: 0 0.5em;
}
.navbar{
.navbar {
z-index: 4 !important;
}
.app-container {
.namespace-container{
.namespace-container {
background: $vikunja-nav-background;
z-index: 6;
color: $vikunja-nav-color;
@ -107,7 +120,7 @@
}
}
.menu{
.menu {
.menu-label {
font-size: 1em;
font-weight: 700;
@ -169,12 +182,12 @@
}
}
.nsettings{
.nsettings {
float: right;
padding: 10px 0.3em 0;
}
.menu-label,.nsettings,.menu-list a{
.menu-label, .nsettings, .menu-list a {
color: $vikunja-nav-color;
}
@ -240,6 +253,7 @@
&.router-link-exact-active {
color: $primary;
border-left: $vikunja-nav-selected-width solid darken($primary, 5%);
.icon {
color: $primary;
}
@ -280,9 +294,7 @@
}
}
.navbar .user{
padding: 1em 0.5em 0;
.navbar .user {
span {
font-family: $vikunja-font;
}
@ -306,7 +318,7 @@
.dropdown-trigger .button {
background: none;
&:focus:not(:active), &:active{
&:focus:not(:active), &:active {
outline: none !important;
-webkit-box-shadow: none !important;
-moz-box-shadow: none !important;
@ -321,57 +333,59 @@
}
}
.mobilemenu-hide-button,.mobilemenu-show-button{
.mobilemenu-hide-button, .mobilemenu-show-button {
display: none;
position: fixed;
z-index: 31;
font-weight: bold;
font-size: 2em;
color: $dark;
&:hover, &:focus{
&:hover, &:focus {
color: darken($dark, 20);
}
}
.mobilemenu-hide-button{
.mobilemenu-hide-button {
color: $dark;
&:hover, &:focus{
&:hover, &:focus {
color: $dark;
}
}
.mobile-overlay{
.mobile-overlay {
display: none;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(250,250,250,0.8);
background: rgba(250, 250, 250, 0.8);
z-index: 5;
opacity: 0;
transition: all $transition;
}
@media screen and (max-width: $tablet) {
.mobilemenu-hide-button{
.mobilemenu-hide-button {
display: block;
top: 1vh;
right: 4vh;
}
.mobilemenu-show-button{
.mobilemenu-show-button {
display: block;
top: 1vh;
left: 4vh;
}
.mobile-overlay{
.mobile-overlay {
display: block;
opacity: 1;
}
.navbar.is-dark .navbar-brand > .navbar-item{
.navbar.is-dark .navbar-brand > .navbar-item {
margin: 0 auto;
}
}

View File

@ -57,3 +57,7 @@ button.table {
background-color: transparent;
}
}
.pagination {
padding-bottom: 1em;
}

View File

@ -18,6 +18,7 @@ $navbar-dropdown-boxed-shadow: $dropdown-content-shadow;
$bulmaswatch-import-font: false !default;
$light-background: #F1F5F8;
$transition-duration: 100ms;
$transparent-background-light: rgba($light-background, 0.9);
$vikunja-font: 'Quicksand', sans-serif;
$vikunja-light-text: darken(#fff, 10%);
@ -43,4 +44,6 @@ $scrollbar-track-color: lighten($dark, 65);
$scrollbar-thumb-color: lighten($dark, 40);
$scrollbar-hover-color: lighten($dark, 30);
$button-height: 2.648em;
$button-height: 2.648em;
$switch-view-height: 43px;