work on weather services; outbound http request caching; styles updates and icons
This commit is contained in:
60
templates/color-theme-controls.css
Normal file
60
templates/color-theme-controls.css
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
fieldset.radio {
|
||||
width: 30rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
column-gap: 2%;
|
||||
border-color: var(--theme-border-input);
|
||||
}
|
||||
|
||||
fieldset.radio.theme {
|
||||
margin-block: 1rem 2rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
fieldset.radio {
|
||||
width: calc(100vw - 5rem);
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
width: 32%;
|
||||
height: 2rem;
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
input {
|
||||
appearance: none;
|
||||
display: block;
|
||||
background: transparent;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
input:not(:checked) {
|
||||
cursor: pointer;
|
||||
background: var(--theme-bg-button-primary);
|
||||
}
|
||||
|
||||
input:checked {
|
||||
background: var(--theme-bg-button-primary-hover);
|
||||
}
|
||||
|
||||
span {
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
text-align: center;
|
||||
display: block;
|
||||
height: 100%;
|
||||
line-height: 2rem;
|
||||
}
|
7
templates/color-theme-controls.html.mustache
Normal file
7
templates/color-theme-controls.html.mustache
Normal file
@@ -0,0 +1,7 @@
|
||||
<a class="popup-open color-theme-contrast" href="#popup-color-theme-contrast">Colors and Contrast</a>
|
||||
<div class="popup" id="popup-color-theme-contrast" role="dialog">
|
||||
<div class="popup-content">
|
||||
<a class="popup-close" href="#">Close</a>
|
||||
<color-theme-and-contrast-controls></color-theme-and-contrast-controls>
|
||||
</div>
|
||||
</div>
|
130
templates/color-theme-controls.js
Normal file
130
templates/color-theme-controls.js
Normal file
@@ -0,0 +1,130 @@
|
||||
(() => {
|
||||
|
||||
const color_theme = 'color_theme';
|
||||
const color_contrast = 'color_contrast';
|
||||
const color_theme_attr = 'data-color-theme';
|
||||
const color_contrast_attr = 'data-color-contrast';
|
||||
|
||||
const override_theme = localStorage.getItem(color_theme);
|
||||
const override_contrast = localStorage.getItem(color_contrast);
|
||||
|
||||
if (override_theme) {
|
||||
document.body.setAttribute(color_theme_attr, override_theme);
|
||||
}
|
||||
|
||||
if (override_contrast) {
|
||||
document.body.setAttribute(color_contrast_attr, override_contrast);
|
||||
}
|
||||
|
||||
const template = `
|
||||
<link rel="stylesheet" href="/typography.css">
|
||||
<link rel="stylesheet" href="/color-theme-controls.css">
|
||||
<div class="popup" title="Color Theme and Contrast Controls">
|
||||
<fieldset class="theme radio">
|
||||
<legend>Color Theme</legend>
|
||||
<label>
|
||||
<input type="radio" name="color_theme" value="auto" ${checked_if(! override_theme)}>
|
||||
<span>Auto</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="color_theme" value="light" ${checked_if(override_theme === 'light')}>
|
||||
<span>Light</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="color_theme" value="dark" ${checked_if(override_theme === 'dark')}>
|
||||
<span>Dark</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset class="contrast radio">
|
||||
<legend>Color Contrast</legend>
|
||||
<label>
|
||||
<input type="radio" name="color_contrast" value="less" ${checked_if(override_contrast === 'less')}>
|
||||
<span>Less Contrast</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="color_contrast" value="default" ${checked_if(! override_contrast)}>
|
||||
<span>Default</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="color_contrast" value="more" ${checked_if(override_contrast === 'more')}>
|
||||
<span>More Contrast</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
`;
|
||||
|
||||
customElements.define('color-theme-and-contrast-controls',
|
||||
class ColorThemeToggleButton extends HTMLElement {
|
||||
#popup = null;
|
||||
#theme_inputs = null;
|
||||
#contrast_inputs = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this.shadowRoot.innerHTML = template;
|
||||
this.#popup = this.shadowRoot.querySelector('.popup');
|
||||
this.#theme_inputs = this.#popup.querySelectorAll('.theme input');
|
||||
this.#contrast_inputs = this.#popup.querySelectorAll('.contrast input');
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.#popup.addEventListener('change', this.#onChange);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.#popup.removeEventListener('change', this.#onChange);
|
||||
}
|
||||
|
||||
#onChange = () => {
|
||||
const theme = this.#theme_input_value;
|
||||
const contrast = this.#contrast_input_value;
|
||||
|
||||
if (theme === 'auto') {
|
||||
localStorage.removeItem(color_theme);
|
||||
document.body.removeAttribute(color_theme_attr);
|
||||
}
|
||||
|
||||
else {
|
||||
localStorage.setItem(color_theme, theme);
|
||||
document.body.setAttribute(color_theme_attr, theme);
|
||||
}
|
||||
|
||||
if (contrast === 'default') {
|
||||
localStorage.removeItem(color_contrast);
|
||||
document.body.removeAttribute(color_contrast_attr);
|
||||
}
|
||||
|
||||
else {
|
||||
localStorage.setItem(color_contrast, contrast);
|
||||
document.body.setAttribute(color_contrast_attr, theme);
|
||||
}
|
||||
};
|
||||
|
||||
get #theme_input_value() {
|
||||
return radio_value(this.#theme_inputs, 'auto');
|
||||
}
|
||||
|
||||
get #contrast_input_value() {
|
||||
return radio_value(this.#contrast_inputs, 'auto');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function checked_if(condition) {
|
||||
return condition ? 'checked' : '';
|
||||
}
|
||||
|
||||
function radio_value(inputs, default_value) {
|
||||
inputs = [ ...inputs ];
|
||||
|
||||
for (const input of inputs) {
|
||||
if (input.checked) {
|
||||
return input.value;
|
||||
}
|
||||
}
|
||||
|
||||
return default_value;
|
||||
}
|
||||
|
||||
})();
|
22
templates/controls.html.mustache
Normal file
22
templates/controls.html.mustache
Normal file
@@ -0,0 +1,22 @@
|
||||
<aside class="controls">
|
||||
<div>
|
||||
{{# user }}
|
||||
<p>Logged in as
|
||||
{{# user.name }}{{ user.name }}{{/ user.name }}
|
||||
{{^ user.name }}{{ user.username }}{{/ user.name }}
|
||||
-
|
||||
</p>
|
||||
<form action="/logout" method="POST">
|
||||
<button type="submit">Logout</button>
|
||||
</form>
|
||||
{{/ user }}
|
||||
|
||||
{{^ user }}
|
||||
<form action="/login" method="POST">
|
||||
<button type="submit">Login with OpenID Connect</button>
|
||||
</form>
|
||||
{{/ user }}
|
||||
</div>
|
||||
|
||||
{{> color_theme_controls }}
|
||||
</aside>
|
143
templates/forms.css
Normal file
143
templates/forms.css
Normal file
@@ -0,0 +1,143 @@
|
||||
|
||||
fieldset {
|
||||
border-color: var(--theme-border-input);
|
||||
}
|
||||
|
||||
button, input:is([type='button'], [type='submit'], [type='reset']) {
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
label {
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
|
||||
label:not(:first-of-type) {
|
||||
margin-block-start: 2rem;
|
||||
}
|
||||
|
||||
label.radio {
|
||||
display: flex;
|
||||
margin-block: 1rem;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
display: block;
|
||||
margin-block-start: 0.35rem;
|
||||
margin-block-end: 1rem;
|
||||
border: 0.125rem var(--theme-border-input) solid;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--theme-bg-input);
|
||||
padding: 0.5rem;
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
input[disabled],
|
||||
textarea[disabled] {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input[type='text']:invalid,
|
||||
input[type='password']:invalid,
|
||||
textarea:invalid {
|
||||
border-color: var(--theme-border-input-invalid);
|
||||
}
|
||||
|
||||
input[type='text'][readonly] {
|
||||
color: var(--theme-text-light);
|
||||
border-color: var(--theme-line);
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type='checkbox'] + label {
|
||||
margin-inline-start: 0.5rem;
|
||||
margin-inline-end: 2rem;
|
||||
}
|
||||
|
||||
input[type='radio'] {
|
||||
flex: 0 0 2rem;
|
||||
margin: 0.2rem;
|
||||
}
|
||||
|
||||
textarea {
|
||||
line-height: 1.7rem;
|
||||
font-family: var(--font-monospace);
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
color: var(--theme-text-light);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.5rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
margin-block-start: 1rem;
|
||||
margin-inline-end: 1rem;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
appearance: none;
|
||||
color: var(--theme-text-button-primary);
|
||||
background: var(--theme-bg-button-primary);
|
||||
text-decoration: none;
|
||||
font-size: 1rem;
|
||||
font-family: var(--font-body);
|
||||
font-weight: 700;
|
||||
line-height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
button[disabled] {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
select {
|
||||
display: block;
|
||||
margin-block-start: 0.35rem;
|
||||
margin-block-end: 1rem;
|
||||
border: 0.125rem var(--theme-border-input) solid;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--theme-bg-input);
|
||||
padding: 0.5rem;
|
||||
width: 20rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select[disabled] {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
select option {
|
||||
background-color: var(--theme-bg-input);
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
:is(table, .table) :is(input, select) {
|
||||
margin-block: 0;
|
||||
}
|
||||
*/
|
3
templates/local-clock/local-clock.html.mustache
Normal file
3
templates/local-clock/local-clock.html.mustache
Normal file
@@ -0,0 +1,3 @@
|
||||
<section data-widget="local-clock" title="Local-time clock">
|
||||
{{! }}
|
||||
</section>
|
5
templates/login-failed.html.mustache
Normal file
5
templates/login-failed.html.mustache
Normal file
@@ -0,0 +1,5 @@
|
||||
<aside class="error">
|
||||
<h2>Login Failed</h2>
|
||||
<p><b>Error Code:</b> {{ login_error_code }}</p>
|
||||
<p><b>Error Message:</b> {{ login_error.message }}</p>
|
||||
</aside>
|
@@ -1,21 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Login</title>
|
||||
<link rel="stylesheet" type="text/css" href="/themes.css">
|
||||
<link rel="stylesheet" type="text/css" href="/typography.css">
|
||||
</head>
|
||||
<body>
|
||||
<form action="/login" method="POST">
|
||||
<button type="submit">Login with OpenID Connect</button>
|
||||
</form>
|
||||
|
||||
{{# error_code }}
|
||||
<div>
|
||||
<h4>Login failed</h4>
|
||||
<b>Error Code:</b> {{ error_code }}<br />
|
||||
<b>Error Message:</b> {{ error.message }}
|
||||
</div>
|
||||
{{/ error_code }}
|
||||
</body>
|
||||
</html>
|
16
templates/page.html.mustache
Normal file
16
templates/page.html.mustache
Normal file
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ page_title }}</title>
|
||||
<link rel="stylesheet" href="/themes.css">
|
||||
<link rel="stylesheet" href="/typography.css">
|
||||
<link rel="stylesheet" href="/forms.css">
|
||||
<link rel="stylesheet" href="/popup.css">
|
||||
<link rel="stylesheet" href="/structure.css">
|
||||
<script src="/color-theme-controls.js" async></script>
|
||||
</head>
|
||||
<body>
|
||||
{{> controls }}
|
||||
{{> page_content }}
|
||||
</body>
|
||||
</html>
|
37
templates/popup.css
Normal file
37
templates/popup.css
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
.popup {
|
||||
z-index: 1;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
opacity: 0;
|
||||
transition: opacity 0.125s linear;
|
||||
background: var(--theme-bg-popup-mask);
|
||||
}
|
||||
|
||||
.popup:target {
|
||||
opacity: 1;
|
||||
user-select: unset;
|
||||
pointer-events: unset;
|
||||
}
|
||||
|
||||
.popup .popup-content {
|
||||
margin: auto;
|
||||
position: relative;
|
||||
background: var(--theme-bg-main);
|
||||
border: 0.125rem solid var(--theme-border-input);
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.popup .popup-close {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 2rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
@@ -1,22 +1,9 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dashboard</title>
|
||||
<link rel="stylesheet" type="text/css" href="/themes.css">
|
||||
<link rel="stylesheet" type="text/css" href="/typography.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Dashboard</h1>
|
||||
</header>
|
||||
|
||||
{{# user }}
|
||||
<p>Logged in as {{ user.name }} ({{ user.username }})</p>
|
||||
<form action="/logout" method="POST">
|
||||
<button type="submit">Logout</button>
|
||||
</form>
|
||||
{{/ user }}
|
||||
|
||||
{{^ user }}
|
||||
<a href="/login">Login Page</a>
|
||||
{{/ user }}
|
||||
</body>
|
||||
</html>
|
||||
<main>
|
||||
{{# rendered_widgets }}
|
||||
{{{ . }}}
|
||||
{{/ rendered_widgets }}
|
||||
</main>
|
64
templates/structure.css
Normal file
64
templates/structure.css
Normal file
@@ -0,0 +1,64 @@
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
background: var(--theme-bg-main);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ===== Primary Controls ===== */
|
||||
|
||||
aside.controls {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
aside.controls :is(a, p, button) {
|
||||
display: inline;
|
||||
font-size: 0.8rem;
|
||||
margin-block: 0.5rem;
|
||||
}
|
||||
|
||||
aside.controls form {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
aside.controls button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--theme-text-link);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
aside.controls button:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ===== Error Box ===== */
|
||||
|
||||
aside.error {
|
||||
margin-block: 4rem;
|
||||
margin-inline: 1rem;
|
||||
padding: 1rem;
|
||||
border: 0.1rem solid var(--theme-border-error-box);
|
||||
background: var(--theme-bg-error-box);
|
||||
}
|
||||
|
||||
aside.error h2 {
|
||||
font-size: 1.2rem;
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
aside.error p {
|
||||
margin-block: 0;
|
||||
color: var(--theme-text-error-box);
|
||||
}
|
@@ -3,76 +3,60 @@
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
|
||||
|
||||
{{! ===== Default Themes ===== }}
|
||||
|
||||
body {
|
||||
{{> default_light }}
|
||||
}
|
||||
|
||||
body[data-color-scheme='dark'] {
|
||||
body[data-color-theme='dark'] {
|
||||
{{> default_dark }}
|
||||
}
|
||||
|
||||
{{# less_contrast }}
|
||||
body[data-color-contrast='less'] {
|
||||
{{> less_contrast_light }}
|
||||
}
|
||||
|
||||
body[data-color-theme='dark'][data-color-contrast='less'] {
|
||||
{{> less_contrast_dark }}
|
||||
}
|
||||
{{/ less_contrast }}
|
||||
|
||||
{{# more_contrast }}
|
||||
body[data-color-contrast='more'] {
|
||||
{{> more_contrast_light }}
|
||||
}
|
||||
|
||||
body[data-color-theme='dark'][data-color-contrast='more'] {
|
||||
{{> more_contrast_dark }}
|
||||
}
|
||||
{{/ more_contrast }}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
{{> default_dark }}
|
||||
}
|
||||
|
||||
body[data-color-scheme='light'] {
|
||||
body[data-color-theme='light'] {
|
||||
{{> default_light }}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
{{! ===== High Contrast Themes ===== }}
|
||||
|
||||
{{# more_contrast }}
|
||||
@media (prefers-contrast: more) {
|
||||
body {
|
||||
{{> more_contrast_light }}
|
||||
}
|
||||
|
||||
body[data-color-scheme='dark'] {
|
||||
{{> more_contrast_dark }}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
{{> more_contrast_dark }}
|
||||
}
|
||||
|
||||
body[data-color-scheme='light'] {
|
||||
{{> more_contrast_light }}
|
||||
}
|
||||
}
|
||||
}
|
||||
{{/ more_contrast }}
|
||||
|
||||
|
||||
|
||||
{{! ===== Low Contrast Themes ===== }}
|
||||
|
||||
{{# less_contrast }}
|
||||
@media (prefers-contrast: less) {
|
||||
body {
|
||||
{{> less_contrast_light }}
|
||||
}
|
||||
|
||||
body[data-color-scheme='dark'] {
|
||||
{{# less_contrast }}
|
||||
body[data-color-contrast='less'] {
|
||||
{{> less_contrast_dark }}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
{{> less_contrast_dark }}
|
||||
}
|
||||
|
||||
body[data-color-scheme='light'] {
|
||||
{{> less_contrast_light }}
|
||||
}
|
||||
body[data-color-theme='light'][data-color-contrast='less'] {
|
||||
{{> less_contrast_light }}
|
||||
}
|
||||
{{/ less_contrast }}
|
||||
|
||||
{{# more_contrast }}
|
||||
body[data-color-contrast='more'] {
|
||||
{{> more_contrast_dark }}
|
||||
}
|
||||
|
||||
body[data-color-theme='light'][data-color-contrast='more'] {
|
||||
{{> more_contrast_light }}
|
||||
}
|
||||
{{/ more_contrast }}
|
||||
}
|
||||
{{/ less_contrast }}
|
||||
|
@@ -1,17 +1,36 @@
|
||||
|
||||
:root {
|
||||
--font-heading: 'Open Sans', sans-serif;
|
||||
--font-body: 'Open Sans', sans-serif;
|
||||
--font-heading: Verdana, sans-serif;
|
||||
--font-body: Verdana, sans-serif;
|
||||
--font-monospace: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
|
||||
font-size: 16px;
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
:root {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 2000px) {
|
||||
:root {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ===== Font Families ===== */
|
||||
|
||||
h1, h2, h3, h4, h5, h6,
|
||||
th, dt {
|
||||
font-family: var(--font-heading);
|
||||
}
|
||||
|
||||
p, td, dd, figcaption, li, blockquote {
|
||||
p, td, dd, figcaption, li, blockquote,
|
||||
input, textarea, select, option, optgroup, legend, fieldset, label, button {
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
@@ -24,3 +43,110 @@ b, i, u, q,
|
||||
strong, em, mark, cite {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ===== Font Sizes ===== */
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
th, td, dt, dd,
|
||||
p, figcaption, li, blockquote,
|
||||
pre,
|
||||
input, textarea, select, option, optgroup, legend, fieldset, label, button {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
a, span,
|
||||
b, i, u, q,
|
||||
strong, em, mark, cite,
|
||||
code, samp {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ===== Colors ===== */
|
||||
|
||||
::selection {
|
||||
color: var(--theme-text-selection);
|
||||
background: var(--theme-bg-text-selection);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6,
|
||||
th, dt {
|
||||
color: var(--theme-text-heading)
|
||||
}
|
||||
|
||||
p, td, dd, figcaption, li, blockquote,
|
||||
input, textarea, select, option, optgroup, legend, fieldset, label {
|
||||
color: var(--theme-text-body)
|
||||
}
|
||||
|
||||
button, input:is([type='button'], [type='submit'], [type='reset']) {
|
||||
color: var(--theme-text-button-primary);
|
||||
background: var(--theme-bg-button-primary);
|
||||
}
|
||||
|
||||
:is(button, input:is([type='button'], [type='submit'], [type='reset'])):hover {
|
||||
background: var(--theme-bg-button-primary-hover);
|
||||
}
|
||||
|
||||
:is(button, input:is([type='button'], [type='submit'], [type='reset'])).secondary {
|
||||
color: var(--theme-text-button-secondary);
|
||||
background: var(--theme-bg-button-secondary);
|
||||
}
|
||||
|
||||
:is(button, input:is([type='button'], [type='submit'], [type='reset'])).secondary:hover {
|
||||
background: var(--theme-bg-button-secondary-hover);
|
||||
}
|
||||
|
||||
pre, code, samp {
|
||||
color: var(--theme-code-normal);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--theme-text-link);
|
||||
}
|
||||
|
||||
a:active {
|
||||
color: var(--theme-text-link-active);
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: var(--theme-text-link-visited);
|
||||
}
|
||||
|
||||
mark {
|
||||
color: var(--theme-text-highlight);
|
||||
background: var(--theme-bg-text-highlight);
|
||||
}
|
||||
|
||||
span,
|
||||
b, i, u, q,
|
||||
strong, em, cite {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
|
78
templates/weather.gov/forecast.html.mustache
Normal file
78
templates/weather.gov/forecast.html.mustache
Normal file
@@ -0,0 +1,78 @@
|
||||
<section data-widget="weather-gov-forecast" title="Weather Forecast for {{ location.name }}">
|
||||
<link rel="stylesheet" href="/weather.gov/styles.css">
|
||||
|
||||
<div class="flex-row">
|
||||
<div class="today">
|
||||
{{# forecast_today }}
|
||||
<div class="{{# isDaytime }}day{{/ isDaytime }}{{^ isDaytime }}night{{/ isDaytime }}">
|
||||
<h2>{{ name }}</h2>
|
||||
<p class="condition">{{ shortForecast }}</p>
|
||||
<p class="temp">
|
||||
{{{ icons.thermometer }}}
|
||||
<span>{{ temperature }}<sup>{{ temperatureUnit }}</sup></span>
|
||||
</p>
|
||||
<p class="wind">
|
||||
{{{ icons.wind }}}
|
||||
<span>{{ windSpeed }} <!-- / {{ windDirection }} --></span>
|
||||
</p>
|
||||
</div>
|
||||
{{/ forecast_today }}
|
||||
</div>
|
||||
|
||||
<table class="future">
|
||||
<tr class="day">
|
||||
{{# forecast_days }}
|
||||
<td>
|
||||
{{# . }}
|
||||
<h3>{{ name }}</h3>
|
||||
<p class="condition">{{ shortForecast }}</p>
|
||||
<p class="temp">
|
||||
{{{ icons.thermometer }}}
|
||||
<span>{{ temperature }}<sup>{{ temperatureUnit }}</sup></span>
|
||||
</p>
|
||||
<p class="wind">
|
||||
{{{ icons.wind }}}
|
||||
<span>{{ windSpeed }} <!-- / {{ windDirection }} --></span>
|
||||
</p>
|
||||
{{/ . }}
|
||||
</td>
|
||||
{{/ forecast_days }}
|
||||
</tr>
|
||||
<tr class="night">
|
||||
{{# forecast_nights }}
|
||||
<td>
|
||||
{{# . }}
|
||||
<h3>{{ name }}</h3>
|
||||
<p class="condition">{{ shortForecast }}</p>
|
||||
<p class="temp">
|
||||
{{{ icons.thermometer }}}
|
||||
<span>{{ temperature }}<sup>{{ temperatureUnit }}</sup></span>
|
||||
</p>
|
||||
<p class="wind">
|
||||
{{{ icons.wind }}}
|
||||
<span>{{ windSpeed }} <!-- / {{ windDirection }} --></span>
|
||||
</p>
|
||||
{{/ . }}
|
||||
</td>
|
||||
{{/ forecast_nights }}
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{# alerts.length }}
|
||||
<ul class="alerts" title="Alerts">
|
||||
{{# alerts }}
|
||||
<li>
|
||||
<h2>{{ event }}</h2>
|
||||
<p class="headline">{{ headline }}</p>
|
||||
<p class="description">{{ description }}</p>
|
||||
<p class="instruction">{{ instruction }}</p>
|
||||
</li>
|
||||
{{/ alerts }}
|
||||
</ul>
|
||||
{{/ alerts.length }}
|
||||
|
||||
<p class="powered-by">
|
||||
Powered by <a href="https://www.weather.gov/documentation/services-web-api" rel="external nofollow noreferrer">weather.gov</a>
|
||||
</p>
|
||||
</section>
|
0
templates/weather.gov/hourly-forecast.html.mustache
Normal file
0
templates/weather.gov/hourly-forecast.html.mustache
Normal file
83
templates/weather.gov/styles.css
Normal file
83
templates/weather.gov/styles.css
Normal file
@@ -0,0 +1,83 @@
|
||||
|
||||
[data-widget='weather-gov-forecast'] {
|
||||
padding: 1rem;
|
||||
max-width: 70rem;
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] .alerts {
|
||||
margin-block: 1rem;
|
||||
padding-inline: 1rem;
|
||||
border: 0.1rem solid var(--theme-border-error-box);
|
||||
background: var(--theme-bg-error-box);
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] .alerts li {
|
||||
margin-block: 1rem;
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] .alerts p {
|
||||
color: var(--theme-text-error-box);
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] .alerts p.headline {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] .flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] .today {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
row-gap: 2rem;
|
||||
padding: 1rem;
|
||||
margin-block-end: 1rem;
|
||||
background: var(--theme-bg-light);
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] svg.icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
flex: 0 0 1rem;
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] h2 {
|
||||
font-size: 1.2rem;
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] .today p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] .today svg.icon {
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] h3 {
|
||||
font-size: 0.8rem;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] p {
|
||||
font-size: 0.8rem;
|
||||
margin-block: 0.25rem;
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] p:is(.wind, .temp) {
|
||||
display: flex;
|
||||
column-gap: 0.5rem;
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] table {
|
||||
margin-block-end: 1rem;
|
||||
}
|
||||
|
||||
[data-widget='weather-gov-forecast'] td {
|
||||
padding-inline: 1rem;
|
||||
}
|
21
templates/weatherapi.com/current.html.mustache
Normal file
21
templates/weatherapi.com/current.html.mustache
Normal file
@@ -0,0 +1,21 @@
|
||||
<section data-widget="weatherapi-com-current" title="Current Weather for {{ location.name }}">
|
||||
<link rel="stylesheet" href="/weatherapi.com/styles.css">
|
||||
|
||||
<p class="condition">
|
||||
<span>{{ weather.current.condition.text }}</span>
|
||||
</p>
|
||||
|
||||
<p class="temp">
|
||||
{{{ icons.thermometer }}}
|
||||
<span>{{ weather.current.temp_f }}<sup>F</sup> / {{ weather.current.temp_c }}<sup>C</sup></span>
|
||||
</p>
|
||||
|
||||
<p class="wind">
|
||||
{{{ icons.wind }}}
|
||||
<span>{{ weather.current.wind_mph }} mph / {{ weather.current.wind_kph }} km/h / {{ weather.current.wind_dir }}</span>
|
||||
</p>
|
||||
|
||||
<p class="powered-by">
|
||||
Powered by <a href="https://www.weatherapi.com/docs/" rel="external nofollow noreferrer">WeatherAPI.com</a>
|
||||
</p>
|
||||
</section>
|
32
templates/weatherapi.com/styles.css
Normal file
32
templates/weatherapi.com/styles.css
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
[data-widget='weatherapi-com-current'] {
|
||||
width: 20rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
[data-widget='weatherapi-com-current'] svg.icon {
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
flex: 0 0 1.2rem;
|
||||
}
|
||||
|
||||
[data-widget='weatherapi-com-current'] h2 {
|
||||
font-size: 1.2rem;
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
[data-widget='weatherapi-com-current'] p:is(.condition, .wind, .temp) {
|
||||
display: flex;
|
||||
column-gap: 0.5rem;
|
||||
font-size: 1rem;
|
||||
margin-block: 0.25rem;
|
||||
}
|
||||
|
||||
[data-widget='weatherapi-com-current'] p.condition {
|
||||
margin-block-end: 1rem;
|
||||
}
|
||||
|
||||
[data-widget='weatherapi-com-current'] p.powered-by {
|
||||
font-size: 0.8rem;
|
||||
margin-block-start: 1rem;
|
||||
}
|
Reference in New Issue
Block a user