summaryrefslogtreecommitdiffhomepage
path: root/themes
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2020-03-23 22:31:50 +0100
committerJo-Philipp Wich <jo@mein.io>2020-03-26 09:54:40 +0100
commit63b5f63a420a4f096dda4e67bf811ba2d1e842df (patch)
tree31ad354f556ce598f5f2a0f0484f2502e722eac4 /themes
parent0fb2f8f1d1d8748b56522cc81cf68d5aa04b9f50 (diff)
themes: add new theme OpenWrt 2020
Introduce a new theme modelled after the logo guidelines published in https://openwrt.org/_media/docs/guide-graphic-designer/openwrt-logo-usage-guidelines.pdf Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'themes')
-rw-r--r--themes/luci-theme-openwrt-2020/Makefile14
-rw-r--r--themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/GalanoGrotesqueW00-Regular.woff2bin0 -> 29004 bytes
-rw-r--r--themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/cascade.css1816
-rw-r--r--themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/favicon.pngbin0 -> 2184 bytes
-rw-r--r--themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/logo.svg7
-rw-r--r--themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/spinner.svg12
-rw-r--r--themes/luci-theme-openwrt-2020/luasrc/view/themes/openwrt2020/footer.htm16
-rw-r--r--themes/luci-theme-openwrt-2020/luasrc/view/themes/openwrt2020/header.htm236
-rwxr-xr-xthemes/luci-theme-openwrt-2020/root/etc/uci-defaults/30_luci-theme-openwrt-202011
9 files changed, 2112 insertions, 0 deletions
diff --git a/themes/luci-theme-openwrt-2020/Makefile b/themes/luci-theme-openwrt-2020/Makefile
new file mode 100644
index 000000000..dcbe11012
--- /dev/null
+++ b/themes/luci-theme-openwrt-2020/Makefile
@@ -0,0 +1,14 @@
+#
+# Copyright (C) 2020 Jo-Philipp Wich <jo@mein.io>
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI modern OpenWrt theme
+LUCI_DEPENDS:=
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/GalanoGrotesqueW00-Regular.woff2 b/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/GalanoGrotesqueW00-Regular.woff2
new file mode 100644
index 000000000..950ac98cc
--- /dev/null
+++ b/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/GalanoGrotesqueW00-Regular.woff2
Binary files differ
diff --git a/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/cascade.css b/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/cascade.css
new file mode 100644
index 000000000..eb25f5975
--- /dev/null
+++ b/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/cascade.css
@@ -0,0 +1,1816 @@
+:root {
+ --main-bright-color: #00A3E1;
+ --main-dark-color: #002B49;
+ --secondary-bright-color: #FFFFFF;
+ --secondary-dark-color: #212322;
+ --danger-color: #CC1111;
+ --warning-color: #CC8800;
+ --regular-font: "GalanoGrotesqueW00-Regular";
+ --base-font-size: 16px;
+}
+
+@font-face {
+ font-family: "GalanoGrotesqueW00-Regular";
+ src: url("GalanoGrotesqueW00-Regular.woff2") format("woff2");
+}
+
+/*
+ * resets and base style
+ */
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ text-decoration: none;
+ list-style: none;
+ color: inherit;
+ font-family: var(--regular-font), "sans-serif";
+ border: none;
+ font-size: 100%;
+ background: none;
+ outline: none;
+ -webkit-appearance: none;
+ -webkit-text-size-adjust: none;
+}
+
+html {
+ height: 100%;
+ width: 100%;
+ max-width: 1366px;
+ margin: 0 auto;
+ background: #fff linear-gradient(90deg, rgba(0, 0, 0, .8), rgba(0, 0, 0 ,.5), rgba(0, 0, 0, .8));
+}
+
+body {
+ background: var(--secondary-bright-color);
+ color: var(--secondary-dark-color);
+ font-size: var(--base-font-size);
+ cursor: default;
+ display: inline-flex;
+ flex-direction: column;
+ min-height: 100%;
+ min-width: 100%;
+}
+
+/*
+ * scaffholding
+ */
+
+#menubar {
+ background: var(--main-bright-color) url(logo.svg) 10px center/50px 50px no-repeat;
+ padding: 0 1em 0 70px;
+ min-height: 70px;
+ display: flex;
+ align-items: center;
+ color: var(--secondary-bright-color);
+ flex: 0;
+ width: 100%;
+ box-shadow: inset 0 0 1px var(--main-dark-color);
+}
+
+#menubar > * {
+ flex: 1 1 auto;
+}
+
+#menubar .hostname {
+ font-weight: bold;
+ font-size: 2em;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+#menubar .distversion {
+ flex: 3;
+}
+
+#menubar .indicators {
+ flex: 1 1 25%;
+ text-align: right;
+}
+
+#menubar .indicators > * {
+ background: var(--secondary-bright-color);
+ color: var(--main-bright-color);
+ display: inline-block;
+ font-size: .85em;
+ line-height: 1.5em;
+ padding: 0 .5em;
+ margin: .125em;
+ border-radius: 1em;
+ cursor: pointer;
+ white-space: nowrap;
+}
+
+#menubar .indicators > * > #xhr_poll_status_off {
+ background: var(--main-bright-color);
+ color: var(--secondary-bright-color);
+ border-radius: 1em;
+ margin: 0 -.5em;
+ display: block;
+ padding: 0 .5em;
+ border: 2px solid var(--secondary-bright-color);
+ line-height: calc(1.5em - 4px);
+}
+
+#menubar h2,
+.skiplink {
+ display: none;
+}
+
+#maincontainer {
+ flex-direction: row;
+ display: inline-flex;
+ flex: 1 0 auto;
+}
+
+#mainmenu {
+ flex: 1 1 200px;
+ background: var(--main-dark-color);
+ color: var(--main-bright-color);
+ padding: 1em;
+}
+
+#mainmenu > div {
+ position: sticky;
+ top: 1em;
+}
+
+#mainmenu ul {
+ padding: 0;
+ margin: 0 0 .5em .5em;
+ line-height: 1.5em;
+}
+
+#mainmenu ul > li {
+ list-style: none;
+}
+
+#mainmenu li > ul {
+ max-height: 0;
+ overflow: hidden;
+ transition: max-height .1s ease-in-out;
+}
+
+#mainmenu li.selected > a {
+ color: var(--secondary-bright-color);
+}
+
+#mainmenu ul:not(.active) > li.selected > ul,
+#mainmenu li.active > ul {
+ max-height: 3000px;
+ transition: max-height 1s ease-in-out;
+}
+
+#mainmenu .l1 > li > a {
+ font-weight: bold;
+ font-size: 1.05em;
+}
+
+#maincontent {
+ flex: 10;
+ padding: 1em 1em 0 1em;
+}
+
+body > .luci {
+ flex: 0;
+ font-size: .7em;
+ padding: .25em;
+ text-align: right;
+ background: var(--main-bright-color);
+ color: var(--secondary-bright-color);
+ margin: 0;
+}
+
+/*
+ * modal
+ */
+
+body.modal-overlay-active {
+ overflow: hidden;
+}
+
+body.modal-overlay-active #modal_overlay {
+ left: 0;
+ right: 0;
+ opacity: 1;
+}
+
+#modal_overlay {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: -10000px;
+ right: 10000px;
+ background: rgba(0, 0, 0, 0.7);
+ z-index: 10000;
+ overflow-y: scroll;
+ -webkit-overflow-scrolling: touch;
+ transition: opacity .125s ease-in;
+ opacity: 0;
+}
+
+#modal_overlay > .modal {
+ max-width: 80%;
+ margin: 10% auto 25% auto;
+ background: var(--secondary-bright-color);
+ box-shadow: 0 0 3px 1px var(--main-bright-color);
+ padding: .5em;
+ border-radius: .25em;
+}
+
+.modal > h4:first-child {
+ padding: .5rem;
+ margin: -.5rem -.5rem .5rem -.5rem;
+ background: var(--main-bright-color);
+ color: var(--secondary-bright-color);
+ border-radius: .25rem .25rem 0 0;
+}
+
+.modal > *:first-child:last-child {
+ margin: .5em 0 !important;
+}
+
+.modal .cbi-section > legend:first-child { font-size: 120%; }
+
+
+/*
+ * table layout
+ */
+
+.table {
+ display: table;
+ width: 100%;
+ margin: 0 0 1rem 0;
+ position: relative;
+}
+
+.tr {
+ display: table-row;
+}
+
+.tr.cbi-section-table-titles[data-title]::before {
+ font-weight: bold;
+ border-top: none;
+}
+
+.tr[data-title]::before {
+ content: attr(data-title);
+ display: table-cell;
+ border-top: 1px solid var(--main-dark-color);
+ padding: .5em;
+}
+
+.th {
+ font-weight: bold;
+ display: table-cell;
+ padding: .5em;
+ /* word-break: break-word; */
+}
+
+.cbi-section-table-descr .th {
+ opacity: .8;
+ font-size: 90%;
+ font-weight: normal;
+}
+
+.td {
+ display: table-cell;
+ border-top: 1px solid var(--main-dark-color);
+ padding: .5em;
+ vertical-align: middle;
+}
+
+.td input[type="text"],
+.td input[type="password"],
+.td select,
+.td .cbi-dropdown:not(.btn):not(.cbi-button),
+.td .cbi-dynlist {
+ min-width: auto;
+ width: 100%;
+}
+
+.tr.drag-over-above {
+ box-shadow: 0 -6px 6px var(--main-bright-color);
+}
+
+.tr.drag-over-below {
+ box-shadow: 0 6px 6px var(--main-bright-color);
+}
+
+.tr.placeholder {
+ height: 4em;
+ position: relative;
+}
+
+.tr.placeholder > .td {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ text-align: center;
+ line-height: 3em;
+ font-size: 90%;
+ opacity: .8;
+}
+
+/*
+ * view specific table invariants
+ */
+
+ #cbi-wireless-wifi-device .ifacebadge {
+ flex-direction: column;
+ justify-content: space-around;
+ }
+
+.assoclist .td,
+[data-page="admin-status-overview"] .td {
+ font-size: .9rem;
+ vertical-align: middle;
+}
+
+.assoclist .td:nth-of-type(3) > span {
+ display: block;
+ max-width: 270px;
+ font-size: .8rem;
+}
+
+.assoclist .td:nth-of-type(5) > span {
+ font-size: .8rem;
+}
+
+.assoclist .td > .ifacebadge {
+ flex-wrap: wrap;
+ justify-content: space-around;
+ max-width: 120px;
+ padding: .2em;
+}
+
+.assoclist .td > .ifacebadge::after {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.assoclist .td > .ifacebadge > img {
+ margin: 0 25px;
+}
+
+.assoclist .td > .ifacebadge[data-ssid][data-ifname] > span {
+ display: none;
+}
+
+.assoclist .td > .ifacebadge[data-ssid][data-ifname]::after {
+ content: attr(data-ssid) " (" attr(data-ifname) ")";
+}
+
+[data-page="admin-status-overview"] .td:nth-of-type(3) {
+ min-width: 100px;
+}
+
+[data-page="admin-network-firewall"] .table > .tr > *:nth-child(1) {
+ flex: 1 1 30%;
+}
+
+[data-page="admin-network-wireless"] .cbi-section-actions > div {
+ display: flex;
+}
+
+[data-page="admin-network-wireless"] .cbi-section-actions > div > * {
+ flex: 1;
+}
+
+[data-page="admin-status-processes"] .table .td:nth-of-type(3),
+[data-tab="leases"] .table .td[data-name="duid"] {
+ word-break: break-word;
+}
+
+/*
+ * uci changelog
+ */
+
+.uci-change-list {
+ font-size: 90%;
+ white-space: pre;
+ overflow: hidden;
+}
+
+.uci-change-list del,
+.uci-change-list ins,
+.uci-change-list var,
+.uci-change-legend-label del,
+.uci-change-legend-label ins,
+.uci-change-legend-label var {
+ text-decoration: none;
+ font-family: monospace;
+ font-style: normal;
+ border: 1px solid #ccc;
+ background: #eee;
+ padding: 2px;
+ display: block;
+ line-height: 15px;
+ margin-bottom: 1px;
+}
+
+.uci-change-list h5 {
+ margin: .5em 0 .25em 0;
+}
+
+.uci-change-list ins,
+.uci-change-legend-label ins {
+ border-color: #0f0;
+ background: #cfc;
+}
+
+.uci-change-list del,
+.uci-change-legend-label del {
+ border-color: #f00;
+ background: #fcc;
+}
+
+.uci-change-list var,
+.uci-change-legend-label var {
+ border-color: #ccc;
+ background: #eee;
+}
+
+.uci-change-list var ins,
+.uci-change-list var del {
+ display: inline-block;
+ border: none;
+ width: 100%;
+ padding: 0;
+}
+
+.uci-change-legend {
+ margin: .5em 0 0 0;
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.uci-change-legend-label {
+ flex: 1 1 10em;
+ white-space: nowrap;
+}
+
+.uci-change-legend-label > ins,
+.uci-change-legend-label > del,
+.uci-change-legend-label > var {
+ float: left;
+ margin-right: 4px;
+ width: 16px;
+ height: 16px;
+ display: block;
+ position: relative;
+}
+
+.uci-change-legend-label var ins,
+.uci-change-legend-label var del {
+ border: none;
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ right: 2px;
+ bottom: 2px;
+}
+
+/*
+ * alignment helpers
+ */
+
+.left {
+ text-align: left !important;
+}
+
+.right {
+ text-align: right !important;
+}
+
+.center {
+ text-align: center !important;
+}
+
+.top {
+ vertical-align: top !important;
+}
+
+.bottom {
+ vertical-align: bottom !important;
+}
+
+.middle {
+ vertical-align: middle !important;
+}
+
+.nowrap {
+ white-space: nowrap !important;
+}
+
+.hidden {
+ display: none !important;
+}
+
+/*
+ * legacy hacks
+ */
+
+[width="33%"] {
+ width: 33%;
+ max-width: 33%;
+}
+
+[width="50%"] {
+ width: 50%;
+ max-width: 50%;
+}
+
+[data-name="_freq"] select {
+ min-width: auto;
+}
+
+.cbi-value-field > div:first-child + br {
+ display: none;
+}
+
+/*
+ * typography
+ */
+
+h1, h2, h3, h4, h5, h6,
+.cbi-section > legend:first-child {
+ font-weight: bold;
+ margin: 0 0 1rem 0;
+}
+
+strong, b {
+ font-weight: bold;
+}
+
+h1 { font-size: 160%; }
+h2 { font-size: 150%; }
+h3 { font-size: 140%; }
+h4 { font-size: 130%; }
+h5 { font-size: 120%; }
+h6 { font-size: 110%; }
+
+.cbi-section > legend:first-child { font-size: 140%; }
+
+p, ul {
+ margin: 0 0 1em 0;
+}
+
+var {
+ color: var(--main-dark-color);
+ font-weight: bold;
+}
+
+code {
+ font-family: monospace;
+ color: var(--main-dark-color);
+}
+
+pre {
+ font-family: monospace;
+ margin: 0 0 1em 0;
+ font-size: .9rem;
+ box-shadow: inset 0 0 2px var(--main-dark-color);
+ padding: .25rem;
+ overflow: auto;
+}
+
+big {
+ font-size: 110%;
+}
+
+small {
+ font-size: 95%;
+}
+
+ul {
+ padding: 0 0 0 1.5em;
+}
+
+ul > li {
+ list-style: disc;
+}
+
+/*
+ * widgets
+ */
+
+.ifacebox, .ifacebadge, .zonebadge {
+ display: inline-flex;
+ line-height: 1.8em;
+ padding: 0 .25em;
+ margin: .25em;
+ box-shadow: 0px 0px 2px var(--main-dark-color);
+ font-size: .9em;
+ border-radius: .5em;
+ overflow: hidden;
+ font-size: .8rem;
+ vertical-align: text-top;
+ background: var(--secondary-bright-color);
+ align-items: center;
+ color: var(--secondary-dark-color);
+ vertical-align: middle;
+}
+
+.zonebadge > .ifacebadge {
+ margin: .125em -.125em .125em .35em;
+}
+
+.zonebadge > .ifacebadge > img
+{
+ margin: .125em 0 .125em .25em;
+}
+
+.ifacebox {
+ display: inline-flex;
+ flex-direction: column;
+ padding: 0;
+ text-align: center;
+ width: 100%;
+ max-width: 100px;
+}
+
+.ifacebox-head {
+ background: var(--main-bright-color);
+ width: 100%;
+}
+
+.ifacebox-body {
+ text-align: center;
+ padding: .3em .25em .25em .25em;
+ white-space: nowrap;
+}
+
+.ifacebadge {
+ display: inline-flex;
+ align-items: center;
+}
+
+.ifacebadge.large {
+ line-height: 1.3em;
+}
+
+.ifacebadge > img {
+ vertical-align: text-bottom;
+ margin: .25em;
+ height: 16px;
+}
+
+.ifacebadge > * {
+ margin-left: .25em;
+}
+
+.network-status-table {
+ display: inline-flex;
+ flex-wrap: wrap;
+ width: 100%;
+ margin: 0 -.2em 1em -.2em;
+}
+
+.network-status-table > .ifacebox {
+ max-width: none;
+ flex: 1 1 45%;
+ margin: .25em;
+ min-width: 250px;
+}
+
+.network-status-table > .ifacebox .ifacebadge {
+ font-size: 100%;
+ max-width: none;
+ flex: 1 1 45%;
+ margin: .2em;
+}
+
+.network-status-table .ifacebox-body > div {
+ display: flex;
+ flex-wrap: wrap;
+ margin: .3em -.1em -.1em -.1em;
+}
+
+.cbi-tooltip-container {
+ cursor: help;
+}
+
+.cbi-tooltip {
+ position: absolute;
+ z-index: 10000;
+ left: -10000px;
+ box-shadow: 0 0 2px rgba(0, 0, 0, .7);
+ border-radius: 3px;
+ background: var(--secondary-bright-color);
+ white-space: pre;
+ padding: 2px 5px;
+ opacity: 0;
+ transition: opacity .25s ease-in;
+ font-size: .8rem;
+}
+
+.cbi-tooltip.error {
+ color: var(--danger-color);
+}
+
+.cbi-tooltip-container:hover .cbi-tooltip:not(:empty) {
+ left: auto;
+ opacity: 1;
+ transition: opacity .25s ease-in;
+}
+
+.zone-forwards {
+ display: flex;
+ align-items: center;
+}
+
+.cbi-progressbar {
+ border-radius: .25em;
+ position: relative;
+ min-width: 20rem;
+ height: 1.5em;
+ box-shadow: 0 0 2px var(--main-dark-color);
+ overflow: hidden;
+ margin: .125rem 0;
+}
+
+.cbi-progressbar > div {
+ background: var(--main-bright-color);
+ height: 100%;
+ transition: width .25s ease-in;
+ width: 0%;
+}
+
+.cbi-progressbar::after {
+ position: absolute;
+ bottom: 0;
+ top: 0;
+ right: 0;
+ left: 0;
+ text-align: center;
+ text-shadow: 0 0 2px var(--secondary-bright-color);
+ content: attr(title);
+ white-space: nowrap;
+ line-height: 1.5em;
+}
+
+.cbi-tabmenu {
+ padding: 0;
+ margin: 0 -.5em 1em -.5em;
+ font-weight: bold;
+ color: var(--main-dark-color);
+}
+
+.cbi-tabmenu > li {
+ display: inline-flex;
+ white-space: nowrap;
+ opacity: 1;
+ height: 1.8em;
+}
+
+.cbi-tabmenu > li > a {
+ flex: 1;
+ margin: .1em .5em;
+}
+
+.cbi-tabmenu > .cbi-tab > a {
+ border-bottom: 2px solid var(--main-dark-color);
+}
+
+[data-tab] {
+ display: none;
+ opacity: 0;
+ transition: opacity .25s ease-in-out;
+}
+
+[data-tab-active="true"] {
+ opacity: 1;
+ height: auto;
+ display: block;
+}
+
+.alert-message:not(.modal) {
+ box-shadow: 0 0 3px var(--secondary-dark-color);
+ padding: .5em;
+ margin: 0 0 1em 0;
+ background: var(--warning-color);
+ color: var(--secondary-bright-color);
+ transition: opacity .4s ease;
+}
+
+.alert-message + .alert-message {
+ margin: -.5em 0 1em 0;
+}
+
+.alert-message.info {
+ background: var(--main-bright-color);
+}
+
+.alert-message.warning {
+ background: var(--warning-color);
+}
+
+.alert-message.danger {
+ background: var(--danger-color);
+}
+
+.alert-message .btn {
+ background: inherit;
+ box-shadow: 0 0 2px var(--secondary-bright-color);
+}
+
+.alert-message .btn:hover {
+ box-shadow: 0 0 4px 1px var(--secondary-bright-color);
+}
+
+@keyframes fade-in {
+ 0% { opacity: 0; }
+ 100% { opacity: 1; }
+}
+
+@keyframes fade-out {
+ 0% { opacity: 1; }
+ 100% { opacity: 0; }
+}
+
+.fade-in {
+ animation: fade-in .4s ease;
+}
+
+.fade-out {
+ animation: fade-out .4s ease;
+ opacity: 0;
+}
+
+/*
+ * forms
+ */
+
+button, .btn {
+ background: var(--main-bright-color);
+ color: var(--secondary-bright-color);
+ line-height: 1.5em;
+ border-radius: .25em;
+ cursor: pointer;
+ box-shadow: 0 0 2px var(--main-dark-color);
+ padding: 0 .5em;
+ display: inline-block;
+}
+
+button:hover, .btn:hover {
+ box-shadow: 0 0 6px var(--main-bright-color);
+}
+
+button + button, .btn + .btn, button + .btn, .btn + button, select + button {
+ margin-left: .25em;
+}
+
+button.important {
+ background: var(--main-dark-color);
+}
+
+button[disabled], button.disabled, .btn[disabled], .btn.disabled {
+ pointer-events: none;
+ opacity: .5;
+}
+
+.cbi-button-apply, .cbi-button-positive {
+ background: var(--main-dark-color);
+}
+
+.cbi-button-negative, .cbi-button-remove {
+ background: var(--danger-color);
+}
+
+.cbi-checkbox input[type="checkbox"] {
+ display: none;
+}
+
+.cbi-checkbox input[type="checkbox"] + label {
+ position: relative;
+ display: inline-block;
+ width: 1.3em;
+ height: 1.3em;
+ vertical-align: text-top;
+}
+
+.cbi-checkbox input[type="checkbox"] + label::before {
+ content: "\0a";
+ height: 1em;
+ width: 1em;
+ box-shadow: 0 0 2px var(--main-dark-color);
+ display: inline-block;
+ border-radius: .25em;
+ margin: .15em 0;
+ position: absolute;
+ left: 0;
+ top: 0;
+ cursor: pointer;
+}
+
+.cbi-checkbox input[type="checkbox"]:checked + label::after {
+ content: "\0a";
+ position: absolute;
+ display: inline-block;
+ background: var(--main-dark-color);
+ top: .35em;
+ left: .2em;
+ width: .6em;
+ height: .6em;
+ border-radius: .15em;
+ cursor: pointer;
+}
+
+input[type="text"],
+input[type="password"],
+select,
+.cbi-dropdown:not(.btn):not(.cbi-button) {
+ border-bottom: 2px solid transparent;
+ box-shadow: inset 0 0 1px var(--main-dark-color);
+ padding: 0 .2rem;
+ line-height: 1.5rem;
+ min-height: calc(1.5rem + 2px);
+ min-width: 20rem;
+ border-radius: .25em;
+}
+
+input[type="text"]:focus,
+input[type="password"]:focus,
+select:focus,
+.cbi-dropdown:not(.btn):not(.cbi-button):focus,
+.cbi-dropdown[open]:not(.btn):not(.cbi-button) {
+ border-color: var(--main-dark-color);
+}
+
+input[type="text"] + .btn, input[type="text"] + button,
+input[type="password"] + .btn, input[type="password"] + button {
+ margin: 0 0 2px -1px;
+ background: var(--main-dark-color);
+ border-radius: 0 .25em .25em 0;
+}
+
+.control-group > select + .btn, .control-group > select + button, {
+ margin-left: .25em;
+}
+
+.control-group > input[type="text"] + .btn, .control-group > input[type="text"] + button,
+.control-group > input[type="password"] + .btn, .control-group > input[type="password"] + button {
+ margin: .125em .125em calc(.125em + 2px) calc(-.125em - .25em) !important;
+}
+
+input[type="checkbox"] {
+ height: 1em;
+ vertical-align: middle;
+ -webkit-appearance: checkbox;
+}
+
+select {
+ padding: .1rem 0;
+ -webkit-appearance: menulist;
+}
+
+textarea {
+ width: 100%;
+ box-shadow: inset 0 0 2px var(--main-dark-color);
+ font-family: monospace;
+ font-size: .9rem;
+ padding: .2rem;
+}
+
+.cbi-input-invalid,
+.cbi-input-invalid:focus {
+ color: var(--danger-color);
+ border-color: var(--danger-color) !important;
+ box-shadow: inset 0 0 2px var(--danger-color);
+}
+
+.control-group {
+ display: inline-flex;
+ margin: 0 -.125rem;
+}
+
+.control-group > *,
+.control-group > .cbi-dropdown > ul > li {
+ justify-content: space-around;
+}
+
+.control-group > * {
+ margin: .125rem !important;
+ min-width: auto;
+}
+
+.control-group > select,
+.control-group > input[type="text"] {
+ flex: 10;
+}
+
+.cbi-value {
+ display: flex;
+ flex-wrap: wrap;
+ margin: 0 0 1em 0;
+}
+
+.cbi-value > label:first-child {
+ flex: 1 1 40%;
+ padding: 0 .5em 0 0;
+}
+
+.cbi-value > .cbi-value-field {
+ flex: 2 2 55%;
+}
+
+.cbi-value > .cbi-section {
+ flex: 1 1 100%;
+}
+
+.cbi-map-descr,
+.cbi-tab-descr,
+.cbi-section-descr,
+.cbi-value-description,
+.cbi-value[data-widget="CBI.DummyValue"] > div:first-child {
+ opacity: .8;
+ font-size: .9rem;
+ padding: .2em 0;
+}
+
+.cbi-map-descr,
+.cbi-tab-descr,
+.cbi-section-descr,
+.cbi-section-table,
+.cbi-section-create {
+ margin: 0 0 1em 0;
+}
+
+.cbi-dynlist {
+ display: inline-block;
+ font-size: 90%;
+ min-height: calc(1.5em + 2px);
+ line-height: 1.5em;
+ min-width: 20rem;
+ flex-wrap: wrap;
+}
+
+.cbi-dynlist > .item {
+ box-shadow: 0 0 2px var(--main-dark-color);
+ margin: .3em 0;
+ padding: .15em .2em;
+ border-radius: .25em;
+ position: relative;
+ overflow: hidden;
+ transition: box-shadow .25s ease-in-out;
+ pointer-events: none;
+ flex: 1 1 100%;
+}
+
+.cbi-dynlist > .item::after {
+ content: "-";
+ top: 0;
+ right: 0;
+ bottom: 0;
+ width: 1.6rem;
+ background: var(--main-bright-color);
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ position: absolute;
+ box-shadow: 0 0 2px var(--main-dark-color);
+ text-align: center;
+ color: var(--secondary-bright-color);
+ cursor: pointer;
+ pointer-events: all;
+}
+
+.cbi-dynlist > .item:hover {
+ box-shadow: 0 0 2px var(--main-bright-color);
+}
+
+.cbi-dynlist > .add-item {
+ flex: 1;
+ display: flex;
+}
+
+.cbi-dynlist > .add-item > input {
+ flex: 1;
+ min-width: 18.5rem;
+ border-radius: .25rem 0 0 .25rem;
+}
+
+.cbi-dynlist > .add-item > .btn {
+ flex: 0 0 1.6rem;
+ margin: 0 0 2px -1px;
+ width: 1.6rem;
+ text-align: center;
+}
+
+.cbi-dropdown {
+ display: inline-flex !important;
+ cursor: pointer;
+ height: auto;
+ position: relative;
+ padding: 0 !important;
+}
+
+.cbi-dropdown:not(.btn):not(.cbi-button) {
+ box-shadow: inset 0 0 1px var(--main-dark-color);
+}
+
+.cbi-dropdown > ul {
+ margin: 0 !important;
+ padding: 0;
+ list-style: none;
+ overflow-x: hidden;
+ overflow-y: auto;
+ display: flex;
+ width: 100%;
+}
+
+.cbi-dropdown.btn > ul:not(.dropdown) {
+ padding-left: .5em;
+}
+
+.cbi-dropdown.btn.spinning > ul:not(.dropdown) {
+ padding-left: 0;
+}
+
+.cbi-dropdown.btn > ul.dropdown > li {
+ color: var(--main-dark-color);
+}
+
+.cbi-dropdown > ul.preview {
+ display: none;
+}
+
+.cbi-dropdown > .open,
+.cbi-dropdown > .more {
+ flex-grow: 0;
+ flex-shrink: 0;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ text-align: center;
+ padding: 0 .25em;
+}
+
+.cbi-dropdown.btn > .open,
+.cbi-dropdown.cbi-button > .open {
+ padding: 0 .5em;
+ margin-left: .5em;
+ border-left: 1px solid;
+}
+
+.cbi-dropdown > .more,
+.cbi-dropdown:not(.btn):not(.cbi-button) > ul > li[placeholder] {
+ display: none;
+ justify-content: center;
+ color: rgba(0, 0, 0, .5);
+}
+
+.cbi-dropdown > ul > li {
+ display: none;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ flex-shrink: 1;
+ flex-grow: 1;
+ align-items: center;
+ align-self: center;
+ color: inherit;
+}
+
+.cbi-dropdown > ul.dropdown > li,
+.cbi-dropdown:not(.btn):not(.cbi-button) > ul > li {
+ padding: 0 .25em;
+}
+
+.cbi-dropdown > ul > li .hide-open { display: block; display: initial; }
+.cbi-dropdown > ul > li .hide-close { display: none; }
+
+.cbi-dropdown > ul > li[display]:not([display="0"]) {
+ border-left: 1px solid #ccc;
+}
+
+.cbi-dropdown[empty] > ul {
+ max-width: 1px;
+ max-height: 1.5em;
+}
+
+.cbi-dropdown > ul > li > form {
+ display: none;
+ margin: 0;
+ padding: 0;
+ pointer-events: none;
+}
+
+.cbi-dropdown > ul > li img {
+ align-self: center;
+ margin-right: .25em;
+}
+
+.cbi-dropdown > ul > li input[type="text"] {
+ margin: .25em 0;
+ border: none;
+ background: var(--secondary-bright-color);
+}
+
+.cbi-dropdown[open] {
+ position: relative;
+}
+
+.cbi-dropdown[open] > ul.dropdown {
+ display: block;
+ background: var(--secondary-bright-color);
+ box-shadow: 0 0 1px var(--main-dark-color), 0 0 4px rgba(0, 0, 0, .7);
+ position: absolute;
+ z-index: 1100;
+ max-width: none;
+ min-width: 100%;
+ width: auto;
+ transition: max-height .125s ease-in;
+}
+
+.cbi-dropdown > ul > li[display],
+.cbi-dropdown[open] > ul.preview,
+.cbi-dropdown[open] > ul.dropdown > li,
+.cbi-dropdown[multiple] > ul > li > label,
+.cbi-dropdown[multiple][open] > ul.dropdown > li,
+.cbi-dropdown[multiple][more] > .more,
+.cbi-dropdown[multiple][empty] > .more {
+ flex-grow: 1;
+ display: flex !important;
+}
+
+.cbi-dropdown[empty] > ul > li,
+.cbi-dropdown[optional][open] > ul.dropdown > li[placeholder],
+.cbi-dropdown[multiple][open] > ul.dropdown > li > form {
+ display: block !important;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li .hide-open { display: none; }
+.cbi-dropdown[open] > ul.dropdown > li .hide-close { display: block; display: initial; }
+
+.cbi-dropdown[open] > ul.dropdown > li {
+ border-bottom: 1px solid #ccc;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li[selected] {
+ background: var(--main-dark-color);
+ color: var(--secondary-bright-color);
+}
+
+.cbi-dropdown[open] > ul.dropdown > li.focus {
+ background: var(--main-bright-color);
+}
+
+.cbi-dropdown[open] > ul.dropdown > li:last-child {
+ margin-bottom: 0;
+ border-bottom: none;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li[unselectable] {
+ opacity: 0.7;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li > input.create-item-input:first-child:last-child {
+ width: 100%;
+}
+
+.cbi-dropdown[disabled] {
+ pointer-events: none;
+ opacity: .6;
+}
+
+.cbi-filebrowser {
+ max-width: 100%;
+ width: 1px;
+ box-shadow: 0 0 2px var(--main-dark-color);
+ border-radius: .25rem;
+ display: flex;
+ flex-direction: column;
+ opacity: 0;
+ height: 0;
+ overflow: hidden;
+}
+
+.cbi-filebrowser.open {
+ min-width: 20rem;
+ width: auto;
+ opacity: 1;
+ height: auto;
+ overflow: visible;
+ transition: opacity .25s ease-in;
+}
+
+.cbi-filebrowser > * {
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding: 0 0 .25em 0;
+ margin: .25em .25em 0px .25em;
+ white-space: nowrap;
+ border-bottom: 1px solid var(--main-dark-color);
+}
+
+.cbi-filebrowser .cbi-button-positive {
+ margin-right: .25em;
+}
+
+.cbi-filebrowser > div {
+ border-bottom: none;
+}
+
+.cbi-filebrowser > ul > li {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+.cbi-filebrowser > ul > li a:hover {
+ font-weight: bold;
+ text-decoration: underline;
+}
+
+.cbi-filebrowser > ul > li > div:first-child {
+ flex: 10;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.cbi-filebrowser > ul > li > div:last-child {
+ flex: 3 3 10em;
+ text-align: right;
+}
+
+.cbi-filebrowser > ul > li > div:last-child > button {
+ padding: .125em .25em;
+ margin: 1px 0 1px .25em;
+}
+
+.cbi-filebrowser .upload {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ margin: 0 -.125em .25em -.125em;
+ padding: 0 0 .125em 0px;
+ border-bottom: 1px solid var(--main-dark-color);
+}
+
+.cbi-filebrowser .upload > * {
+ margin: .125em;
+ flex: 1;
+}
+
+.cbi-filebrowser .upload > div > input {
+ width: 100%;
+}
+
+.cbi-section-actions {
+ text-align: right;
+}
+
+.cbi-page-actions {
+ flex-wrap: wrap;
+ width: 100%;
+ justify-content: flex-end;
+ margin-bottom: 1em;
+ margin-top: 1em;
+ border-top: 1px solid var(--main-dark-color);
+ padding-top: 1em;
+ text-align: right;
+}
+
+div[id$=".ipaddr"] > input,
+.cbi-value-field > div > input[type="password"] {
+ min-width: 18.5rem;
+ border-radius: .25rem 0 0 .25rem;
+}
+
+div[id$=".txpower"] {
+ flex-wrap: wrap;
+ align-items: center;
+}
+
+div[id$=".txpower"] > span {
+ white-space: nowrap;
+ margin-left: .25em;
+}
+
+div[id$=".editlist"] {
+ flex: 1;
+}
+
+[data-errors]::after {
+ content: attr(data-errors);
+ background: var(--danger-color);
+ color: var(--secondary-bright-color);
+ border-radius: .6rem;
+ height: 1.1rem;
+ padding: 0 .25rem;
+ font-size: .9rem;
+ display: inline-block;
+ font-weight: bold;
+ min-width: .6rem;
+ line-height: 1rem;
+ margin: -.1rem 0 0 -.2rem;
+ text-align: center;
+}
+
+@keyframes spin {
+ 100% { transform: rotate(360deg); }
+}
+
+.spinning {
+ position: relative;
+ padding-left: 2.1em !important;
+}
+
+.spinning::before {
+ position: absolute;
+ display: flex;
+ align-items: center;
+ top: 0;
+ bottom: 0;
+ left: .4em;
+ width: 1.3em;
+ animation: spin 1s linear infinite;
+ content: url("/luci-static/openwrt2020/spinner.svg");
+}
+
+button.spinning, .btn.spinning {
+ padding-left: 1.6em !important;
+}
+
+button.spinning::before, .btn.spinning::before {
+ filter: invert(1);
+ left: .2em;
+ width: 1.2em;
+}
+
+#view > div.spinning:first-child {
+ padding: .5em 0;
+}
+
+#view > *:last-child {
+ margin: 0 0 1em 0;
+}
+
+.label {
+ background: var(--main-bright-color);
+ color: var(--secondary-bright-color);
+ font-size: .8rem;
+ padding: 0 .4rem;
+ border-radius: .5rem;
+}
+
+.label.warning {
+ background: var(--danger-color);
+}
+
+ul.deps {
+ margin: 0;
+ padding: 0;
+ font-size: .9rem;
+}
+
+ul.errors {
+ margin: 0 0 1em 0;
+ padding: 0;
+}
+
+@media only screen and (max-width: 800px) {
+ body {
+ padding-top: 70px;
+ }
+
+ #maincontent {
+ padding: .25em;
+ max-width: 100vw;
+ }
+
+ #menubar {
+ background: var(--main-bright-color);
+ padding: 0 .5em;
+ position: fixed;
+ top: 0;
+ z-index: 1000;
+ }
+
+ #menubar > h2 {
+ flex: 0 0 2em;
+ display: block;
+ border: 2px solid var(--secondary-bright-color);
+ color: var(--secondary-bright-color);
+ border-radius: .5em;
+ cursor: pointer;
+ font-size: 100%;
+ margin: 0 1em 0 0;
+ }
+
+ #menubar > h2:hover {
+ border-color: var(--secondary-bright-color);
+ color: var(--secondary-bright-color);
+ }
+
+ #menubar > h2 > * {
+ display: none;
+ }
+
+ #menubar > h2::before {
+ content: "☰";
+ width: 35px;
+ line-height: 35px;
+ text-align: center;
+ display: inline-block;
+ color: inherit;
+ font-weight: bold;
+ }
+
+ #menubar > h2.active::before {
+ content: "×";
+ font-size: 200%;
+ }
+
+ #menubar .hostname {
+ font-size: 1.6em;
+ }
+
+ .distversion {
+ display: none;
+ }
+
+ #mainmenu {
+ overflow-x: hidden;
+ overflow-y: auto;
+ max-width: 0;
+ padding: 1em 0;
+ transition: max-width .25s ease-in-out, padding .25s ease-in-out;
+ position: fixed;
+ z-index: 900;
+ height: 100%;
+ }
+
+ #mainmenu.active {
+ max-width: 200px;
+ padding: 1em 1em calc(1em + 70px) 1em;
+ overflow-x: visible;
+ }
+
+ #mainmenu > div {
+ position: static;
+ }
+
+ #mainmenu ul > li {
+ padding: .25em 0;
+ }
+
+ .hide-xs {
+ display: none !important;
+ }
+
+ .table {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .tr {
+ display: block;
+ border-bottom: 1px solid var(--main-dark-color);
+ margin-bottom: .5em;
+ padding-bottom: .5em;
+ }
+
+ .tr.cbi-section-table-titles[data-title]::before,
+ .tr.cbi-section-table-titles,
+ .tr.cbi-section-table-descr {
+ display: none;
+ }
+
+ .tr[data-title]::before {
+ display: block;
+ font-weight: bold;
+ border-top: none;
+ padding: .4em 0;
+ font-size: 110%;
+ }
+
+ .td {
+ display: block;
+ border-top: none;
+ text-align: left !important;
+ padding: .2em 0;
+ }
+
+ .th, .table-titles {
+ display: none;
+ }
+
+ .td[data-title] {
+ position: relative;
+ padding: .2em 0 .2em 40%;
+ }
+
+ .td[data-title]::before {
+ content: attr(data-title) ": ";
+ white-space: nowrap;
+ font-weight: bold;
+ width: 40%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ padding: .2em 0;
+ text-align: left;
+ display: inline-flex;
+ align-items: center;
+ }
+
+ [data-page="admin-status-overview"] .cbi-section:nth-of-type(1) .td:first-of-type,
+ [data-page="admin-status-overview"] .cbi-section:nth-of-type(2) .td:first-of-type {
+ font-weight: bold;
+ max-width: none;
+ width: 100%;
+ }
+
+ [data-page="admin-status-overview"] .td > span > span { font-size: .9rem; }
+
+ [data-page="admin-status-routes"] .table:nth-of-type(3) .td:nth-of-type(1) { word-break: break-all; }
+
+ [data-page="admin-network-firewall-zones"] .td[data-name="_info"] {
+ padding: .2em 0;
+ line-height: 2.2rem;
+ }
+
+ [data-page="admin-network-firewall-zones"] .td[data-name="_info"]::before {
+ display: none;
+ }
+
+ [data-page="admin-network-firewall-zones"] .td[data-name="_info"] label {
+ font-size: 1rem;
+ }
+
+ #cbi-wireless-wifi-device .tr { display: flex; flex-wrap: wrap; }
+ #cbi-wireless-wifi-device .tr > *:nth-child(1) { flex: 1 1 20%; align-self: center; }
+ #cbi-wireless-wifi-device .tr > *:nth-child(2) { flex: 2 2 75%; }
+ #cbi-wireless-wifi-device .tr > *:nth-child(3) { flex: 3 3 100%; }
+
+ #cbi-network-interface .tr { display: flex; flex-wrap: wrap; }
+ #cbi-network-interface .tr > *:nth-child(1) { flex: 1 1 33%; align-self: center; }
+ #cbi-network-interface .tr > *:nth-child(2) { flex: 2 2 60%; align-self: center; font-size: .9rem; overflow: hidden; }
+ #cbi-network-interface .tr > *:nth-child(3) { flex: 3 3 100%; }
+ #cbi-network-interface .tr > *:nth-child(2) > div { overflow: hidden; text-overflow: ellipsis; }
+
+ .assoclist .tr {
+ display: flex;
+ flex-wrap: wrap;
+ }
+
+ .assoclist .td > .ifacebadge {
+ max-width: 90px;
+ }
+
+ .assoclist .td > .ifacebadge > img {
+ margin: 0 35px;
+ }
+
+ .assoclist .td > .ifacebadge > span {
+ display: none;
+ }
+
+ .assoclist .td > .ifacebadge[data-ifname]::after {
+ content: attr(data-ifname);
+ }
+
+ .assoclist .td > .ifacebadge[data-signal]::after {
+ content: attr(data-signal) " dBm";
+ }
+
+ .assoclist .td:nth-of-type(3) {
+ font-weight: bold;
+ font-size: 1rem;
+ }
+
+ .assoclist .td:nth-of-type(1), .assoclist .td:nth-of-type(4) {
+ flex: 1 1 100px;
+ margin-right: .5em;
+ }
+
+ .assoclist .td:nth-of-type(3), .assoclist .td:nth-of-type(5) {
+ flex: 2 2 calc(100% - 110px);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ align-self: center;
+ }
+
+ .assoclist .td:nth-of-type(6) { flex: 1; text-align: right !important; }
+ .assoclist .td[data-title] { padding: .2em 0; }
+ .assoclist .td[data-title]::before { display: none; }
+
+ .leases6 .td:nth-of-type(3) { word-wrap: break-word; }
+
+ .td.cbi-section-actions > div { display: flex; }
+ .td.cbi-section-actions > div > * { flex: 1; }
+
+ body.modal-overlay-active #modal_overlay > .modal {
+ max-width: 95%;
+ margin: 5% auto;
+ }
+
+ input[type="text"],
+ input[type="password"],
+ select,
+ .cbi-dropdown:not(.btn):not(.cbi-button),
+ .cbi-dynlist {
+ min-height: calc(2.2rem + 2px);
+ line-height: 2.2rem;
+ font-size: 1.2rem;
+ min-width: 10rem;
+ }
+
+ button, .btn {
+ line-height: 1.8rem;
+ font-size: 1.2rem;
+ }
+
+ select {
+ padding: .4em 0;
+ }
+
+ .cbi-value > .cbi-value-field {
+ flex: 1 0 100%;
+ display: flex;
+ flex-direction: column;
+ max-width: 100%;
+ }
+
+ .cbi-value > .cbi-value-field > div[id] {
+ display: flex;
+ flex-direction: row;
+ }
+
+ .cbi-value > .cbi-value-field > div[id] > input,
+ .cbi-value > .cbi-value-field > div[id] > select,
+ .cbi-value > .cbi-value-field > div[id] > .cbi-filebrowser.open {
+ flex: 1;
+ width: 100%;
+ }
+
+ .cbi-dynlist .item::after,
+ .cbi-dynlist .add-item > .btn {
+ line-height: 2em;
+ flex-basis: 2rem;
+ width: 2rem;
+ }
+
+ .ifacebadge.large {
+ font-size: .9rem;
+ }
+
+ .control-group > *,
+ .control-group > .cbi-dropdown > ul > li {
+ flex: 1;
+ white-space: normal;
+ word-wrap: break-word;
+ }
+
+ .cbi-page-actions .cbi-dropdown,
+ .cbi-page-actions .cbi-button-apply:first-child {
+ flex-basis: 100%;
+ }
+
+ .cbi-checkbox {
+ margin: .25rem;
+ }
+
+ .cbi-tabmenu {
+ margin: 0 -.25em 1em -.25em;
+ }
+
+ .cbi-tooltip {
+ font-size: 1rem;
+ box-shadow: 0 0 4px rgba(0, 0, 0, .7);
+ }
+
+ .cbi-value > label:first-child {
+ padding: 0 0 .5em 0;
+ }
+
+ [data-page="admin-system-admin-sshkeys"] .cbi-dynlist > .item {
+ font-size: .9rem;
+ line-height: 1rem;
+ }
+
+ [data-page="admin-system-opkg"] .control-group {
+ flex-wrap: wrap;
+ }
+
+ [data-page="admin-status-iptables"] h2 + div.right {
+ margin: 0 0 1em 0 !important;
+ display: flex;
+ }
+}
+
+@media only screen and (min-width: 800px) and (max-width: 1200px) {
+ .assoclist .tr > *:nth-of-type(2) {
+ display: none;
+ }
+}
diff --git a/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/favicon.png b/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/favicon.png
new file mode 100644
index 000000000..ee841e823
--- /dev/null
+++ b/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/favicon.png
Binary files differ
diff --git a/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/logo.svg b/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/logo.svg
new file mode 100644
index 000000000..878a39d40
--- /dev/null
+++ b/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/logo.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 81.2 98">
+ <path d="m 40.5,50.8 a 6.6,6.6 0 1 0 0,13.2 6.6,6.6 0 0 0 0,-13.2 m -40.5,-33.9 7,6.8 a 47.5,47.5 0 0 1 67.2,0 l 7,-6.8 a 57.2,57.2 0 0 0 -81.2,0" style="fill:#fff" />
+ <path d="m 12.5,29.2 6.8,7 a 30,30 0 0 1 42.6,0 l 6.8,-7 a 39.7,39.7 0 0 0 -56.3,0" style="fill:#fff" />
+ <path d="m 24.8,41.6 6.8,6.9 a 12.6,12.6 0 0 1 18,0 l 6.8,-6.9 a 22.3,22.3 0 0 0 -31.6,0" style="fill:#fff" />
+ <path d="m 64.9,39.7 a 30.2,30.2 0 1 1 -48.7,0 l -6.9,-7 a 39.9,39.9 0 1 0 62.5,0 z" style="fill:#072342" />
+</svg>
diff --git a/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/spinner.svg b/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/spinner.svg
new file mode 100644
index 000000000..f3b52efac
--- /dev/null
+++ b/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/spinner.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="132 132 264 264">
+ <defs>
+ <radialGradient id="g" cx="0%" cy="0%" r="60%">
+ <stop offset=".8" style="stop-opacity:1" />
+ <stop offset="1" style="stop-opacity:.5" />
+ </radialGradient>
+ </defs>
+ <g>
+ <path style="fill:url(#g)" d="M 264 132 A 132 132 0 0 0 132 264 A 132 132 0 0 0 264 396 A 132 132 0 0 0 396 264 A 132 132 0 0 0 264 132 z M 264 170 A 94 94 0 0 1 359 264 A 94 94 0 0 1 264 359 A 94 94 0 0 1 170 264 A 94 94 0 0 1 264 170 z " />
+ </g>
+</svg>
diff --git a/themes/luci-theme-openwrt-2020/luasrc/view/themes/openwrt2020/footer.htm b/themes/luci-theme-openwrt-2020/luasrc/view/themes/openwrt2020/footer.htm
new file mode 100644
index 000000000..07be0d65a
--- /dev/null
+++ b/themes/luci-theme-openwrt-2020/luasrc/view/themes/openwrt2020/footer.htm
@@ -0,0 +1,16 @@
+<%#
+ Copyright 2020 Jo-Philipp Wich <jo@mein.io>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<div class="clear"></div>
+</div>
+</div>
+
+<p class="luci">
+ <% local ver = require "luci.version" -%>
+ Powered by <%= ver.luciname %> (<%= ver.luciversion %>)
+</p>
+
+</body>
+</html>
diff --git a/themes/luci-theme-openwrt-2020/luasrc/view/themes/openwrt2020/header.htm b/themes/luci-theme-openwrt-2020/luasrc/view/themes/openwrt2020/header.htm
new file mode 100644
index 000000000..ad1a84c40
--- /dev/null
+++ b/themes/luci-theme-openwrt-2020/luasrc/view/themes/openwrt2020/header.htm
@@ -0,0 +1,236 @@
+<%#
+ Copyright 2020 Jo-Philipp Wich <jo@mein.io>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<%
+ local sys = require "luci.sys"
+ local util = require "luci.util"
+ local http = require "luci.http"
+ local disp = require "luci.dispatcher"
+ local ver = require "luci.version"
+
+ local boardinfo = util.ubus("system", "board") or { }
+
+ local node = disp.context.dispatched
+ local path = table.concat(disp.context.path, "-")
+
+ http.prepare_content("text/html; charset=UTF-8")
+-%>
+
+<html lang="<%=luci.i18n.context.lang%>">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta http-equiv="Content-Script-Type" content="text/javascript" />
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+<link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/cascade.css" />
+<link rel="icon" href="<%=media%>/favicon.png" type="image/svg+xml" />
+<script type="text/javascript" src="<%=url('admin/translations', luci.i18n.context.lang)%>"></script>
+<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
+<script type="text/javascript">//<![CDATA[
+ (function() {
+ function get_children(node) {
+ var children = [];
+
+ for (var k in node.children) {
+ if (!node.children.hasOwnProperty(k))
+ continue;
+
+ if (!node.children[k].satisfied)
+ continue;
+
+ if (!node.children[k].hasOwnProperty('title'))
+ continue;
+
+ children.push(Object.assign(node.children[k], { name: k }));
+ }
+
+ return children.sort(function(a, b) {
+ return ((a.order || 1000) - (b.order || 1000));
+ });
+ }
+
+ function handle_mainmenu_expand(ev) {
+ var a = ev.target, ul1 = a.parentNode.parentNode, ul2 = a.nextElementSibling;
+
+ document.querySelectorAll('ul.mainmenu.l1 > li.active').forEach(function(li) {
+ if (li !== a.parentNode)
+ li.classList.remove('active');
+ });
+
+ if (!ul2)
+ return;
+
+ if (ul2.parentNode.offsetLeft + ul2.offsetWidth <= ul1.offsetLeft + ul1.offsetWidth)
+ ul2.classList.add('align-left');
+
+ ul1.classList.add('active');
+ a.parentNode.classList.add('active');
+ a.blur();
+
+ ev.preventDefault();
+ ev.stopPropagation();
+ }
+
+ function render_mainmenu(tree, url, level) {
+ var l = (level || 0) + 1,
+ ul = E('ul', { 'class': 'mainmenu l%d'.format(l) }),
+ children = get_children(tree);
+
+ if (children.length == 0 || l > 2)
+ return E([]);
+
+ for (var i = 0; i < children.length; i++) {
+ var isActive = (L.env.dispatchpath[l] == children[i].name),
+ activeClass = 'mainmenu-item-%s%s'.format(children[i].name, isActive ? ' selected' : '');
+
+ ul.appendChild(E('li', { 'class': activeClass }, [
+ E('a', {
+ 'href': L.url(url, children[i].name),
+ 'click': (l == 1) ? handle_mainmenu_expand : null,
+ }, [ _(children[i].title) ]),
+ render_mainmenu(children[i], url + '/' + children[i].name, l)
+ ]));
+ }
+
+ if (l == 1) {
+ var container = document.querySelector('#mainmenu');
+
+ container.firstElementChild.appendChild(ul);
+ container.style.display = '';
+ }
+
+ return ul;
+ }
+
+ function render_modemenu(tree) {
+ var ul = document.querySelector('#modemenu'),
+ children = get_children(tree);
+
+ for (var i = 0; i < children.length; i++) {
+ var isActive = (L.env.requestpath.length ? children[i].name == L.env.requestpath[0] : i == 0);
+
+ ul.appendChild(E('li', {}, [
+ E('a', {
+ 'href': L.url(children[i].name),
+ 'class': isActive ? 'active' : null
+ }, [ _(children[i].title) ])
+ ]));
+
+ if (isActive)
+ render_mainmenu(children[i], children[i].name);
+ }
+
+ if (ul.children.length > 1)
+ ul.style.display = '';
+ }
+
+ function render_tabmenu(tree, url, level) {
+ var container = document.querySelector('#tabmenu'),
+ l = (level || 0) + 1,
+ ul = E('ul', { 'class': 'cbi-tabmenu' }),
+ children = get_children(tree),
+ activeNode = null;
+
+ if (children.length == 0)
+ return E([]);
+
+ for (var i = 0; i < children.length; i++) {
+ var isActive = (L.env.dispatchpath[l + 2] == children[i].name),
+ activeClass = isActive ? ' cbi-tab' : '',
+ className = 'tabmenu-item-%s %s'.format(children[i].name, activeClass);
+
+ ul.appendChild(E('li', { 'class': className }, [
+ E('a', { 'href': L.url(url, children[i].name) }, [ _(children[i].title) ] )
+ ]));
+
+ if (isActive)
+ activeNode = children[i];
+ }
+
+ container.appendChild(ul);
+ container.style.display = '';
+
+ if (activeNode)
+ container.appendChild(render_tabmenu(activeNode, url + '/' + activeNode.name, l));
+
+ return ul;
+ }
+
+ function toggle_sidebar(ev) {
+ var btn = ev.currentTarget,
+ bar = document.querySelector('#mainmenu');
+
+ if (btn.classList.contains('active')) {
+ btn.classList.remove('active');
+ bar.classList.remove('active');
+ }
+ else {
+ btn.classList.add('active');
+ bar.classList.add('active');
+ }
+ }
+
+ document.addEventListener('luci-loaded', function(ev) {
+ var tree = <%= luci.http.write_json(luci.dispatcher.menu_json() or {}) %>,
+ node = tree,
+ url = '';
+
+ render_modemenu(tree);
+
+ if (L.env.dispatchpath.length >= 3) {
+ for (var i = 0; i < 3 && node; i++) {
+ node = node.children[L.env.dispatchpath[i]];
+ url = url + (url ? '/' : '') + L.env.dispatchpath[i];
+ }
+
+ if (node)
+ render_tabmenu(node, url);
+ }
+
+ document.querySelector('#menubar > .navigation').addEventListener('click', toggle_sidebar);
+ });
+ })();
+//]]></script>
+<title><%=striptags( (boardinfo.hostname or "?") .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %> - LuCI</title>
+</head>
+<body class="lang_<%=luci.i18n.context.lang%>" data-page="<%= pcdata(path) %>">
+
+<p class="skiplink">
+<span id="skiplink1"><a href="#navigation"><%:Skip to navigation%></a></span>
+<span id="skiplink2"><a href="#content"><%:Skip to content%></a></span>
+</p>
+
+<div id="menubar">
+ <h2 class="navigation"><a id="navigation" name="navigation"><%:Navigation%></a></h2>
+
+ <span class="hostname"><%=(boardinfo.hostname or "?")%></span>
+ <span class="distversion"><%=ver.distversion%></span>
+ <span class="indicators">
+ <span id="xhr_poll_status" style="display:none" onclick="XHR.running() ? XHR.halt() : XHR.run()">
+ <span id="xhr_poll_status_on" style="display:none"><%:Refreshing%></span>
+ <span id="xhr_poll_status_off" style="display:none"><%:Paused%></span>
+ </span>
+ </span>
+
+ <ul id="modemenu" style="display:none"></ul>
+
+</div>
+
+<div id="maincontainer">
+ <div id="mainmenu" style="display:none">
+ <div></div>
+ </div>
+
+ <div id="maincontent">
+ <%- if luci.sys.process.info("uid") == 0 and luci.sys.user.getuser("root") and not luci.sys.user.getpasswd("root") and category ~= "failsafe" and path ~= "admin-system-admin-password" then -%>
+ <div class="alert-message warning">
+ <h4><%:No password set!%></h4>
+ <p><%:There is no password set on this router. Please configure a root password to protect the web interface and enable SSH.%></p>
+ <% if disp.lookup("admin/system/admin") then %>
+ <div class="right"><a class="btn" href="<%=url("admin/system/admin")%>"><%:Go to password configuration...%></a></div>
+ <% end %>
+ </div>
+ <%- end -%>
+
+ <div id="tabmenu" style="display:none"></div>
diff --git a/themes/luci-theme-openwrt-2020/root/etc/uci-defaults/30_luci-theme-openwrt-2020 b/themes/luci-theme-openwrt-2020/root/etc/uci-defaults/30_luci-theme-openwrt-2020
new file mode 100755
index 000000000..45e742ad7
--- /dev/null
+++ b/themes/luci-theme-openwrt-2020/root/etc/uci-defaults/30_luci-theme-openwrt-2020
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+if [ "$PKG_UPGRADE" != 1 ]; then
+ uci batch <<-EOF
+ set luci.themes.OpenWrt2020=/luci-static/openwrt2020
+ set luci.main.mediaurlbase=/luci-static/openwrt2020
+ commit luci
+ EOF
+fi
+
+exit 0