themes: add new theme OpenWrt 2020 3780/head
authorJo-Philipp Wich <jo@mein.io>
Mon, 23 Mar 2020 21:31:50 +0000 (22:31 +0100)
committerJo-Philipp Wich <jo@mein.io>
Thu, 26 Mar 2020 08:54:40 +0000 (09:54 +0100)
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>
themes/luci-theme-openwrt-2020/Makefile [new file with mode: 0644]
themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/GalanoGrotesqueW00-Regular.woff2 [new file with mode: 0644]
themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/cascade.css [new file with mode: 0644]
themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/favicon.png [new file with mode: 0644]
themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/logo.svg [new file with mode: 0644]
themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/spinner.svg [new file with mode: 0644]
themes/luci-theme-openwrt-2020/luasrc/view/themes/openwrt2020/footer.htm [new file with mode: 0644]
themes/luci-theme-openwrt-2020/luasrc/view/themes/openwrt2020/header.htm [new file with mode: 0644]
themes/luci-theme-openwrt-2020/root/etc/uci-defaults/30_luci-theme-openwrt-2020 [new file with mode: 0755]

diff --git a/themes/luci-theme-openwrt-2020/Makefile b/themes/luci-theme-openwrt-2020/Makefile
new file mode 100644 (file)
index 0000000..dcbe110
--- /dev/null
@@ -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 (file)
index 0000000..950ac98
Binary files /dev/null and b/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/GalanoGrotesqueW00-Regular.woff2 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 (file)
index 0000000..eb25f59
--- /dev/null
@@ -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 (file)
index 0000000..ee841e8
Binary files /dev/null and b/themes/luci-theme-openwrt-2020/htdocs/luci-static/openwrt2020/favicon.png 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 (file)
index 0000000..878a39d
--- /dev/null
@@ -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 (file)
index 0000000..f3b52ef
--- /dev/null
@@ -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 (file)
index 0000000..07be0d6
--- /dev/null
@@ -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 (file)
index 0000000..ad1a84c
--- /dev/null
@@ -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 (executable)
index 0000000..45e742a
--- /dev/null
@@ -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