diff options
author | sadbeast <sadbeast@sadbeast.com> | 2024-01-31 17:47:56 -0800 |
---|---|---|
committer | sadbeast <sadbeast@sadbeast.com> | 2024-01-31 17:47:56 -0800 |
commit | 332ec93366315fa1ed7b4acd7a3407c96e8ddfa7 (patch) | |
tree | 6ae553317f12a7a6a29c849c8805ffab96436dc2 /app | |
download | td-main.tar.gz td-main.tar.bz2 |
Diffstat (limited to 'app')
75 files changed, 4174 insertions, 0 deletions
diff --git a/app/assets/builds/.keep b/app/assets/builds/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/builds/.keep diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 0000000..4028c22 --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js +//= link_tree ../builds diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/images/.keep diff --git a/app/assets/stylesheets/_functions.scss b/app/assets/stylesheets/_functions.scss new file mode 100644 index 0000000..160870c --- /dev/null +++ b/app/assets/stylesheets/_functions.scss @@ -0,0 +1,4 @@ +// Output color in RGB format +@function to-rgb($color) { + @return unquote("rgb(#{red($color)}, #{green($color)}, #{blue($color)})"); +}
\ No newline at end of file diff --git a/app/assets/stylesheets/_variables.scss b/app/assets/stylesheets/_variables.scss new file mode 100644 index 0000000..e2f653e --- /dev/null +++ b/app/assets/stylesheets/_variables.scss @@ -0,0 +1,69 @@ +// Config +// –––––––––––––––––––– + +// Set the root element for $enable-semantic-container and $enable-responsive-spacings +$semantic-root-element: "body" !default; + +// Enable <header>, <main>, <footer> inside $semantic-root-element as containers +$enable-semantic-container: false !default; + +// Enable .container and .container-fluid +$enable-class-container: true !default; + +// Enable a centered viewport for <header>, <main>, <footer> inside $enable-semantic-container +// Fluid layout if disabled +$enable-viewport: true !default; + +// Enable responsive spacings for <header>, <main>, <footer>, <section>, <article> +// Fixed spacings if disabled +$enable-responsive-spacings: true !default; + +// Enable responsive typography +// Fixed root element size if disabled +$enable-responsive-typography: true !default; + +// Enable .classes +// .classless version if disabled +$enable-classes: true !default; + +// Enable .grid class +$enable-grid: true !default; + +// Enable transitions +$enable-transitions: true !default; + +// Enable overriding with !important +$enable-important: true !default; + +// Responsive +// –––––––––––––––––––– + +// xs: Extra small (portrait phones) +// sm: Small(landscape phones) +// md: Medium(tablets) +// lg: Large(desktops) +// xl: Extra large (large desktops) + +// NOTE: +// To provide an easy and fine styling on each breakpoint +// we didn't use @each, @mixin or @include. +// That means you need to edit each CSS selector file to add a breakpoint + +// Breakpoints +// 'null' disable the breakpoint +$breakpoints: ( + xs: 0, + sm: 576px, + md: 768px, + lg: 992px, + xl: 1200px, +) !default; + +// Viewports +$viewports: ( + // 'null' disable the viewport on a breakpoint + sm: 510px, + md: 700px, + lg: 920px, + xl: 1130px +) !default; diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss new file mode 100644 index 0000000..a3ac6ef --- /dev/null +++ b/app/assets/stylesheets/application.scss @@ -0,0 +1,2 @@ +// Sassy +@import "pico"; diff --git a/app/assets/stylesheets/components/_accordion.scss b/app/assets/stylesheets/components/_accordion.scss new file mode 100644 index 0000000..59b2c65 --- /dev/null +++ b/app/assets/stylesheets/components/_accordion.scss @@ -0,0 +1,116 @@ +/** + * Accordion (<details>) + */ + +details { + display: block; + margin-bottom: var(--spacing); + padding-bottom: var(--spacing); + border-bottom: var(--border-width) solid var(--accordion-border-color); + + summary { + line-height: 1rem; + list-style-type: none; + cursor: pointer; + + &:not([role]) { + color: var(--accordion-close-summary-color); + } + + @if $enable-transitions { + transition: color var(--transition); + } + + // Reset marker + &::-webkit-details-marker { + display: none; + } + + &::marker { + display: none; + } + + &::-moz-list-bullet { + list-style-type: none; + } + + // Marker + &::after { + display: block; + width: 1rem; + height: 1rem; + margin-inline-start: calc(var(--spacing, 1rem) * 0.5); + float: right; + transform: rotate(-90deg); + background-image: var(--icon-chevron); + background-position: right center; + background-size: 1rem auto; + background-repeat: no-repeat; + content: ""; + + @if $enable-transitions { + transition: transform var(--transition); + } + } + + &:focus { + outline: none; + + &:not([role="button"]) { + color: var(--accordion-active-summary-color); + } + } + + // Type button + &[role="button"] { + width: 100%; + text-align: left; + + // Marker + &::after { + height: calc(1rem * var(--line-height, 1.5)); + background-image: var(--icon-chevron-button); + } + + @if $enable-classes { + // .contrast + &:not(.outline).contrast { + // Marker + &::after { + background-image: var(--icon-chevron-button-inverse); + } + } + } + } + } + + // Open + &[open] { + > summary { + margin-bottom: calc(var(--spacing)); + + &:not([role]) { + &:not(:focus) { + color: var(--accordion-open-summary-color); + } + } + + &::after { + transform: rotate(0); + } + } + } +} + +[dir="rtl"] { + details { + summary { + text-align: right; + + &::after { + float: left; + background-position: left center; + } + } + } +} diff --git a/app/assets/stylesheets/components/_card.scss b/app/assets/stylesheets/components/_card.scss new file mode 100644 index 0000000..924415e --- /dev/null +++ b/app/assets/stylesheets/components/_card.scss @@ -0,0 +1,36 @@ +/** + * Card (<article>) + */ + +article { + margin: var(--block-spacing-vertical) 0; + padding: var(--block-spacing-vertical) var(--block-spacing-horizontal); + border-radius: var(--border-radius); + background: var(--card-background-color); + box-shadow: var(--card-box-shadow); + + > header, + > footer { + margin-right: calc(var(--block-spacing-horizontal) * -1); + margin-left: calc(var(--block-spacing-horizontal) * -1); + padding: calc(var(--block-spacing-vertical) * 0.66) + var(--block-spacing-horizontal); + background-color: var(--card-sectionning-background-color); + } + + > header { + margin-top: calc(var(--block-spacing-vertical) * -1); + margin-bottom: var(--block-spacing-vertical); + border-bottom: var(--border-width) solid var(--card-border-color); + border-top-right-radius: var(--border-radius); + border-top-left-radius: var(--border-radius); + } + + > footer { + margin-top: var(--block-spacing-vertical); + margin-bottom: calc(var(--block-spacing-vertical) * -1); + border-top: var(--border-width) solid var(--card-border-color); + border-bottom-right-radius: var(--border-radius); + border-bottom-left-radius: var(--border-radius); + } +} diff --git a/app/assets/stylesheets/components/_dropdown.scss b/app/assets/stylesheets/components/_dropdown.scss new file mode 100644 index 0000000..a16e6d2 --- /dev/null +++ b/app/assets/stylesheets/components/_dropdown.scss @@ -0,0 +1,214 @@ +/** + * Dropdown ([role="list"]) + */ + +// Menu +details[role="list"], +li[role="list"] { + position: relative; +} + +details[role="list"] summary + ul, +li[role="list"] > ul { + display: flex; + z-index: 99; + position: absolute; + top: auto; + right: 0; + left: 0; + flex-direction: column; + margin: 0; + padding: 0; + border: var(--border-width) solid var(--dropdown-border-color); + border-radius: var(--border-radius); + border-top-right-radius: 0; + border-top-left-radius: 0; + background-color: var(--dropdown-background-color); + box-shadow: var(--card-box-shadow); + color: var(--dropdown-color); + white-space: nowrap; + + li { + width: 100%; + margin-bottom: 0; + padding: calc(var(--form-element-spacing-vertical) * 0.5) + var(--form-element-spacing-horizontal); + list-style: none; + + &:first-of-type { + margin-top: calc(var(--form-element-spacing-vertical) * 0.5); + } + + &:last-of-type { + margin-bottom: calc(var(--form-element-spacing-vertical) * 0.5); + } + + a { + display: block; + margin: calc(var(--form-element-spacing-vertical) * -0.5) + calc(var(--form-element-spacing-horizontal) * -1); + padding: calc(var(--form-element-spacing-vertical) * 0.5) + var(--form-element-spacing-horizontal); + overflow: hidden; + color: var(--dropdown-color); + text-decoration: none; + text-overflow: ellipsis; + + &:hover { + background-color: var(--dropdown-hover-background-color); + } + } + } +} + +// Marker +details[role="list"] summary, +li[role="list"] > a { + &::after { + display: block; + width: 1rem; + height: calc(1rem * var(--line-height, 1.5)); + margin-inline-start: 0.5rem; + float: right; + transform: rotate(0deg); + background-image: var(--icon-chevron); + background-position: right center; + background-size: 1rem auto; + background-repeat: no-repeat; + content: ""; + } +} + +// Global dropdown only +details[role="list"] { + padding: 0; + border-bottom: none; + + // Style <summary> as <select> + summary { + margin-bottom: 0; + + &:not([role]) { + height: calc( + 1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 + + var(--border-width) * 2 + ); + padding: var(--form-element-spacing-vertical) + var(--form-element-spacing-horizontal); + border: var(--border-width) solid var(--form-element-border-color); + border-radius: var(--border-radius); + background-color: var(--form-element-background-color); + color: var(--form-element-placeholder-color); + line-height: inherit; + cursor: pointer; + + @if $enable-transitions { + transition: background-color var(--transition), + border-color var(--transition), color var(--transition), + box-shadow var(--transition); + } + + &:active, + &:focus { + border-color: var(--form-element-active-border-color); + background-color: var(--form-element-active-background-color); + } + + &:focus { + box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color); + } + } + } + + // Close for details[role="list"] + &[open] summary { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + + &::before { + display: block; + z-index: 1; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: none; + content: ""; + cursor: default; + } + } +} + +// All Dropdowns inside <nav> +nav details[role="list"] summary, +nav li[role="list"] a { + display: flex; + direction: ltr; +} + +nav details[role="list"] summary + ul, +nav li[role="list"] > ul { + min-width: fit-content; + border-radius: var(--border-radius); + + li a { + border-radius: 0; + } +} + +// Dropdowns inside <nav> as nested <details> +nav details[role="list"] { + summary, + summary:not([role]) { + height: auto; + padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal); + } + + &[open] summary { + border-radius: var(--border-radius); + } + + summary + ul { + margin-top: var(--outline-width); + margin-inline-start: 0; + } + + summary[role="link"] { + margin-bottom: calc(var(--nav-link-spacing-vertical) * -1); + line-height: var(--line-height); + + + ul { + margin-top: calc(var(--nav-link-spacing-vertical) + var(--outline-width)); + margin-inline-start: calc(var(--nav-link-spacing-horizontal) * -1); + } + } +} + +// Dropdowns inside a <nav> without using <details> +li[role="list"] { + // Open on hover (for mobile) + // or on active/focus (for keyboard navigation) + &:hover > ul, + a:active ~ ul, + a:focus ~ ul { + display: flex; + } + + > ul { + display: none; + margin-top: calc(var(--nav-link-spacing-vertical) + var(--outline-width)); + margin-inline-start: calc( + var(--nav-element-spacing-horizontal) - var(--nav-link-spacing-horizontal) + ); + } + + > a::after { + background-image: var(--icon-chevron); + } +} + +label > details[role="list"] { + margin-top: calc(var(--spacing) * .25); + margin-bottom: var(--spacing); +} diff --git a/app/assets/stylesheets/components/_modal.scss b/app/assets/stylesheets/components/_modal.scss new file mode 100644 index 0000000..af5cb16 --- /dev/null +++ b/app/assets/stylesheets/components/_modal.scss @@ -0,0 +1,168 @@ +/** + * Modal (<dialog>) + */ + +:root { + --scrollbar-width: 0px; +} + +dialog { + display: flex; + z-index: 999; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + align-items: center; + justify-content: center; + width: inherit; + min-width: 100%; + height: inherit; + min-height: 100%; + padding: var(--spacing); + border: 0; + backdrop-filter: var(--modal-overlay-backdrop-filter); + background-color: var(--modal-overlay-background-color); + color: var(--color); + + // Content + article { + $close-selector: if($enable-classes, ".close", "a[rel='prev']"); + max-height: calc(100vh - var(--spacing) * 2); + overflow: auto; + + @if map-get($breakpoints, "sm") { + @media (min-width: map-get($breakpoints, "sm")) { + max-width: map-get($viewports, "sm"); + } + } + + @if map-get($breakpoints, "md") { + @media (min-width: map-get($breakpoints, "md")) { + max-width: map-get($viewports, "md"); + } + } + + > header, + > footer { + padding: calc(var(--block-spacing-vertical) * 0.5) + var(--block-spacing-horizontal); + } + + > header { + #{$close-selector} { + margin: 0; + margin-left: var(--spacing); + float: right; + } + } + + > footer { + text-align: right; + + [role="button"] { + margin-bottom: 0; + + &:not(:first-of-type) { + margin-left: calc(var(--spacing) * 0.5); + } + } + } + + p { + &:last-of-type { + margin: 0; + } + } + + // Close icon + #{$close-selector} { + display: block; + width: 1rem; + height: 1rem; + margin-top: calc(var(--block-spacing-vertical) * -0.5); + margin-bottom: var(--typography-spacing-vertical); + margin-left: auto; + background-image: var(--icon-close); + background-position: center; + background-size: auto 1rem; + background-repeat: no-repeat; + opacity: 0.5; + + @if $enable-transitions { + transition: opacity var(--transition); + } + + &:is([aria-current], :hover, :active, :focus) { + opacity: 1; + } + } + } + + // Closed state + &:not([open]), + &[open="false"] { + display: none; + } +} + +// Utilities +@if $enable-classes { + .modal-is-open { + padding-right: var(--scrollbar-width, 0px); + overflow: hidden; + pointer-events: none; + touch-action: none; + + dialog { + pointer-events: auto; + } + } +} + +// Animations +@if ($enable-classes and $enable-transitions) { + $animation-duration: 0.2s; + + :where(.modal-is-opening, .modal-is-closing) { + dialog, + dialog > article { + animation-duration: $animation-duration; + animation-timing-function: ease-in-out; + animation-fill-mode: both; + } + + dialog { + animation-duration: ($animation-duration * 4); + animation-name: modal-overlay ; + + > article { + animation-delay: $animation-duration; + animation-name: modal; + } + } + } + + .modal-is-closing { + dialog, + dialog > article { + animation-delay: 0s; + animation-direction: reverse; + } + } + + @keyframes modal-overlay { + from { + backdrop-filter: none; + background-color: transparent; + } + } + + @keyframes modal { + from { + transform: translateY(-100%); + opacity: 0; + } + } +} diff --git a/app/assets/stylesheets/components/_nav.scss b/app/assets/stylesheets/components/_nav.scss new file mode 100644 index 0000000..06fdd22 --- /dev/null +++ b/app/assets/stylesheets/components/_nav.scss @@ -0,0 +1,141 @@ +/** + * Nav + */ + +// Reboot based on : +// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css + +// Prevent VoiceOver from ignoring list semantics in Safari (opinionated) +:where(nav li)::before { + float: left; + content: "\200B"; +} + +// Pico +// –––––––––––––––––––– + +// Horizontal Nav +nav, +nav ul { + display: flex; +} + +nav { + justify-content: space-between; + + ol, + ul { + align-items: center; + margin-bottom: 0; + padding: 0; + list-style: none; + + &:first-of-type { + margin-left: calc(var(--nav-element-spacing-horizontal) * -1); + } + &:last-of-type { + margin-right: calc(var(--nav-element-spacing-horizontal) * -1); + } + } + + li { + display: inline-block; + margin: 0; + padding: var(--nav-element-spacing-vertical) + var(--nav-element-spacing-horizontal); + + // Minimal support for buttons and forms elements + > * { + --spacing: 0; + } + } + + :where(a, [role="link"]) { + display: inline-block; + margin: calc(var(--nav-link-spacing-vertical) * -1) + calc(var(--nav-link-spacing-horizontal) * -1); + padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal); + border-radius: var(--border-radius); + text-decoration: none; + + &:is([aria-current], :hover, :active, :focus) { + text-decoration: none; + } + } + + // Breadcrumb + &[aria-label="breadcrumb"] { + align-items: center; + justify-content: start; + + & ul li { + &:not(:first-child) { + margin-inline-start: var(--nav-link-spacing-horizontal); + } + + &:not(:last-child) { + ::after { + position: absolute; + width: calc(var(--nav-link-spacing-horizontal) * 2); + margin-inline-start: calc(var(--nav-link-spacing-horizontal) / 2); + content: "/"; + color: var(--muted-color); + text-align: center; + } + } + } + + & a[aria-current] { + background-color: transparent; + color: inherit; + text-decoration: none; + pointer-events: none; + } + } + + // Minimal support for role="button" + [role="button"] { + margin-right: inherit; + margin-left: inherit; + padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal); + } +} + +// Vertical Nav +aside { + nav, + ol, + ul, + li { + display: block; + } + + li { + padding: calc(var(--nav-element-spacing-vertical) * 0.5) + var(--nav-element-spacing-horizontal); + + a { + display: block; + } + + // Minimal support for links as buttons + [role="button"] { + margin: inherit; + } + } +} + +// Breadcrumb RTL +[dir="rtl"] { + nav { + &[aria-label="breadcrumb"] { + & ul li { + &:not(:last-child) { + ::after { + content: "\\"; + } + } + } + } + } +} diff --git a/app/assets/stylesheets/components/_progress.scss b/app/assets/stylesheets/components/_progress.scss new file mode 100644 index 0000000..d62ddc0 --- /dev/null +++ b/app/assets/stylesheets/components/_progress.scss @@ -0,0 +1,89 @@ +/** + * Progress + */ + +// Reboot based on : +// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css +// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css +// –––––––––––––––––––– + +// 1. Add the correct display in Edge 18- and IE +// 2. Add the correct vertical alignment in Chrome, Edge, and Firefox +progress { + display: inline-block; // 1 + vertical-align: baseline; // 2 +} + +// Pico +// –––––––––––––––––––– + +progress { + // Reset the default appearance + -webkit-appearance: none; + -moz-appearance: none; + + // Styles + display: inline-block; + appearance: none; + width: 100%; + height: 0.5rem; + margin-bottom: calc(var(--spacing) * 0.5); + overflow: hidden; + + // Remove Firefox and Opera border + border: 0; + border-radius: var(--border-radius); + background-color: var(--progress-background-color); + + // IE10 uses `color` to set the bar background-color + color: var(--progress-color); + + &::-webkit-progress-bar { + border-radius: var(--border-radius); + background: none; + } + &[value]::-webkit-progress-value { + background-color: var(--progress-color); + } + &::-moz-progress-bar { + background-color: var(--progress-color); + } + + // Indeterminate state + @media (prefers-reduced-motion: no-preference) { + &:indeterminate { + background: var(--progress-background-color) + linear-gradient( + to right, + var(--progress-color) 30%, + var(--progress-background-color) 30% + ) + top left / 150% 150% no-repeat; + animation: progress-indeterminate 1s linear infinite; + + &[value]::-webkit-progress-value { + background-color: transparent; + } + &::-moz-progress-bar { + background-color: transparent; + } + } + } +} + +[dir="rtl"] { + @media (prefers-reduced-motion: no-preference) { + progress:indeterminate { + animation-direction: reverse; + } + } +} + +@keyframes progress-indeterminate { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} diff --git a/app/assets/stylesheets/content/_button.scss b/app/assets/stylesheets/content/_button.scss new file mode 100644 index 0000000..075a166 --- /dev/null +++ b/app/assets/stylesheets/content/_button.scss @@ -0,0 +1,183 @@ +/** + * Button + */ + +// Reboot based on : +// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css +// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css +// –––––––––––––––––––– + +// 1. Change the font styles in all browsers +// 2. Remove the margin on controls in Safari +// 3. Show the overflow in Edge +button { + margin: 0; // 2 + overflow: visible; // 3 + font-family: inherit; // 1 + text-transform: none; // 1 +} + +// Correct the inability to style buttons in iOS and Safari +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +// Pico +// –––––––––––––––––––– + +button { + display: block; + width: 100%; + margin-bottom: var(--spacing); +} + +[role="button"] { + display: inline-block; + text-decoration: none; +} + +button, +input[type="submit"], +input[type="button"], +input[type="reset"], +[role="button"] { + --background-color: var(--primary); + --border-color: var(--primary); + --color: var(--primary-inverse); + --box-shadow: var(--button-box-shadow, 0 0 0 rgba(0, 0, 0, 0)); + padding: var(--form-element-spacing-vertical) + var(--form-element-spacing-horizontal); + border: var(--border-width) solid var(--border-color); + border-radius: var(--border-radius); + outline: none; + background-color: var(--background-color); + box-shadow: var(--box-shadow); + color: var(--color); + font-weight: var(--font-weight); + font-size: 1rem; + line-height: var(--line-height); + text-align: center; + cursor: pointer; + + @if $enable-transitions { + transition: background-color var(--transition), + border-color var(--transition), color var(--transition), + box-shadow var(--transition); + } + + &:is([aria-current], :hover, :active, :focus) { + --background-color: var(--primary-hover); + --border-color: var(--primary-hover); + --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)); + --color: var(--primary-inverse); + } + + &:focus { + --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)), + 0 0 0 var(--outline-width) var(--primary-focus); + } +} + +// .secondary, .contrast & .outline +@if $enable-classes { + + // Secondary + :is(button, input[type="submit"], input[type="button"], [role="button"]).secondary, + input[type="reset"] { + --background-color: var(--secondary); + --border-color: var(--secondary); + --color: var(--secondary-inverse); + cursor: pointer; + + &:is([aria-current], :hover, :active, :focus) { + --background-color: var(--secondary-hover); + --border-color: var(--secondary-hover); + --color: var(--secondary-inverse); + } + + &:focus { + --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)), + 0 0 0 var(--outline-width) var(--secondary-focus); + } + } + + // Contrast + :is(button, input[type="submit"], input[type="button"], [role="button"]).contrast { + --background-color: var(--contrast); + --border-color: var(--contrast); + --color: var(--contrast-inverse); + + &:is([aria-current], :hover, :active, :focus) { + --background-color: var(--contrast-hover); + --border-color: var(--contrast-hover); + --color: var(--contrast-inverse); + } + + &:focus { + --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)), + 0 0 0 var(--outline-width) var(--contrast-focus); + } + } + + // Outline (primary) + :is(button, input[type="submit"], input[type="button"], [role="button"]).outline, + input[type="reset"].outline { + --background-color: transparent; + --color: var(--primary); + + &:is([aria-current], :hover, :active, :focus) { + --background-color: transparent; + --color: var(--primary-hover); + } + } + + // Outline (secondary) + :is(button, input[type="submit"], input[type="button"], [role="button"]).outline.secondary, + input[type="reset"].outline { + --color: var(--secondary); + + &:is([aria-current], :hover, :active, :focus) { + --color: var(--secondary-hover); + } + } + + // Outline (contrast) + :is(button, input[type="submit"], input[type="button"], [role="button"]).outline.contrast { + --color: var(--contrast); + + &:is([aria-current], :hover, :active, :focus) { + --color: var(--contrast-hover); + } + } +} +@else { + // Secondary button without .class + input[type="reset"] { + --background-color: var(--secondary); + --border-color: var(--secondary); + --color: var(--secondary-inverse); + cursor: pointer; + + &:is([aria-current], :hover, :active, :focus) { + --background-color: var(--secondary-hover); + --border-color: var(--secondary-hover); + } + + &:focus { + --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)), + 0 0 0 var(--outline-width) var(--secondary-focus); + } + } +} + +// Button [disabled] +// Links without href are disabled by default +:where(button, [type="submit"], [type="button"], [type="reset"], [role="button"])[disabled], +:where(fieldset[disabled]) :is(button, [type="submit"], [type="button"], [type="reset"], [role="button"]), +a[role="button"]:not([href]) { + opacity: 0.5; + pointer-events: none; +} diff --git a/app/assets/stylesheets/content/_code.scss b/app/assets/stylesheets/content/_code.scss new file mode 100644 index 0000000..e03b191 --- /dev/null +++ b/app/assets/stylesheets/content/_code.scss @@ -0,0 +1,91 @@ +/** + * Code + */ + +// Reboot based on : +// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css +// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css +// –––––––––––––––––––– + +// 1. Correct the inheritance and scaling of font size in all browsers +// 2. Correct the odd `em` font sizing in all browsers +pre, +code, +kbd, +samp { + font-size: 0.875em; // 2 + font-family: var(--font-family); // 1 +} + +// Prevent overflow of the container in all browsers (opinionated) +pre { + -ms-overflow-style: scrollbar; + overflow: auto; +} + +// Pico +// –––––––––––––––––––– + +pre, +code, +kbd { + border-radius: var(--border-radius); + background: var(--code-background-color); + color: var(--code-color); + font-weight: var(--font-weight); + line-height: initial; +} + +code, +kbd { + display: inline-block; + padding: 0.375rem 0.5rem; +} + +pre { + display: block; + margin-bottom: var(--spacing); + overflow-x: auto; + + > code { + display: block; + padding: var(--spacing); + background: none; + font-size: 14px; + line-height: var(--line-height); + } +} + +// Code Syntax +code { + // Tags + b { + color: var(--code-tag-color); + font-weight: var(--font-weight); + } + + // Properties + i { + color: var(--code-property-color); + font-style: normal; + } + + // Values + u { + color: var(--code-value-color); + text-decoration: none; + } + + // Comments + em { + color: var(--code-comment-color); + font-style: normal; + } +} + +// kbd +kbd { + background-color: var(--code-kbd-background-color); + color: var(--code-kbd-color); + vertical-align: baseline; +} diff --git a/app/assets/stylesheets/content/_embedded.scss b/app/assets/stylesheets/content/_embedded.scss new file mode 100644 index 0000000..d48293b --- /dev/null +++ b/app/assets/stylesheets/content/_embedded.scss @@ -0,0 +1,48 @@ +/** + * Embedded content + */ + +// Reboot based on : +// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css +// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css +// –––––––––––––––––––– + +// Change the alignment on media elements in all browsers (opinionated) +:where(audio, canvas, iframe, img, svg, video) { + vertical-align: middle; +} + +// Add the correct display in IE 9- +audio, +video { + display: inline-block; +} + +// Add the correct display in iOS 4-7 +audio:not([controls]) { + display: none; + height: 0; +} + +// Remove the border on iframes in all browsers (opinionated) +:where(iframe) { + border-style: none; +} + +// 1. Remove the border on images inside links in IE 10. +// 2. Responsive by default +img { + max-width: 100%; // 2 + height: auto; // 2 + border-style: none; // 1 +} + +// Change the fill color to match the text color in all browsers (opinionated) +:where(svg:not([fill])) { + fill: currentColor; +} + +// Hide the overflow in IE +svg:not(:root) { + overflow: hidden; +} diff --git a/app/assets/stylesheets/content/_form-alt-input-types.scss b/app/assets/stylesheets/content/_form-alt-input-types.scss new file mode 100644 index 0000000..f22151e --- /dev/null +++ b/app/assets/stylesheets/content/_form-alt-input-types.scss @@ -0,0 +1,286 @@ +/** + * Form elements + * Alternatives input types (Not Checkboxes & Radios) + */ + +// Color +[type="color"] { + // Wrapper + @mixin color-wrapper { + padding: 0; + } + + &::-webkit-color-swatch-wrapper { + @include color-wrapper; + } + + &::-moz-focus-inner { + @include color-wrapper; + } + + // Swatch + @mixin color-swatch { + border: 0; + border-radius: calc(var(--border-radius) * 0.5); + } + + &::-webkit-color-swatch { + @include color-swatch; + } + + &::-moz-color-swatch { + @include color-swatch; + } +} + +// Date & Time +// :not() are needed to add Specificity and avoid !important on padding +input:not([type="checkbox"], [type="radio"], [type="range"], [type="file"]) { + &:is([type="date"], [type="datetime-local"], [type="month"], [type="time"], [type="week"]) { + --icon-position: 0.75rem; + --icon-width: 1rem; + padding-right: calc(var(--icon-width) + var(--icon-position)); + background-image: var(--icon-date); + background-position: center right var(--icon-position); + background-size: var(--icon-width) auto; + background-repeat: no-repeat; + } + + // Time + &[type="time"] { + background-image: var(--icon-time); + } +} + +// Calendar picker +[type="date"], +[type="datetime-local"], +[type="month"], +[type="time"], +[type="week"] { + &::-webkit-calendar-picker-indicator { + width: var(--icon-width); + margin-right: calc(var(--icon-width) * -1); + margin-left: var(--icon-position); + opacity: 0; + } +} + +[dir="rtl"] + :is([type="date"], [type="datetime-local"], [type="month"], [type="time"], [type="week"]) { + text-align: right; +} + +// Calendar icons are hidden in Firefox +@if $enable-important { + @-moz-document url-prefix() { + [type="date"], + [type="datetime-local"], + [type="month"], + [type="time"], + [type="week"] { + padding-right: var(--form-element-spacing-horizontal) !important; + background-image: none !important; + } + } +} + +// File +[type="file"] { + --color: var(--muted-color); + padding: calc(var(--form-element-spacing-vertical) * 0.5) 0; + border: 0; + border-radius: 0; + background: none; + + @mixin file-selector-button { + --background-color: var(--secondary); + --border-color: var(--secondary); + --color: var(--secondary-inverse); + margin-right: calc(var(--spacing) / 2); + margin-left: 0; + margin-inline-start: 0; + margin-inline-end: calc(var(--spacing) / 2); + padding: calc(var(--form-element-spacing-vertical) * 0.5) + calc(var(--form-element-spacing-horizontal) * 0.5); + border: var(--border-width) solid var(--border-color); + border-radius: var(--border-radius); + outline: none; + background-color: var(--background-color); + box-shadow: var(--box-shadow); + color: var(--color); + font-weight: var(--font-weight); + font-size: 1rem; + line-height: var(--line-height); + text-align: center; + cursor: pointer; + + @if $enable-transitions { + transition: background-color var(--transition), + border-color var(--transition), color var(--transition), + box-shadow var(--transition); + } + + &:is(:hover, :active, :focus) { + --background-color: var(--secondary-hover); + --border-color: var(--secondary-hover); + } + } + + &::file-selector-button { + @include file-selector-button; + } + + &::-webkit-file-upload-button { + @include file-selector-button; + } + + &::-ms-browse { + @include file-selector-button; + } +} + +// Range +[type="range"] { + // Config + $height-track: 0.25rem; + $height-thumb: 1.25rem; + $border-thumb: 2px; + + // Styles + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + width: 100%; + height: $height-thumb; + background: none; + + // Slider Track + @mixin slider-track { + width: 100%; + height: $height-track; + border-radius: var(--border-radius); + background-color: var(--range-border-color); + + @if $enable-transitions { + transition: background-color var(--transition), + box-shadow var(--transition); + } + } + + &::-webkit-slider-runnable-track { + @include slider-track; + } + + &::-moz-range-track { + @include slider-track; + } + + &::-ms-track { + @include slider-track; + } + + // Slider Thumb + @mixin slider-thumb { + -webkit-appearance: none; + width: $height-thumb; + height: $height-thumb; + margin-top: #{(-($height-thumb * 0.5) + ($height-track * 0.5))}; + border: $border-thumb solid var(--range-thumb-border-color); + border-radius: 50%; + background-color: var(--range-thumb-color); + cursor: pointer; + + @if $enable-transitions { + transition: background-color var(--transition), + transform var(--transition); + } + } + &::-webkit-slider-thumb { + @include slider-thumb; + } + + &::-moz-range-thumb { + @include slider-thumb; + } + + &::-ms-thumb { + @include slider-thumb; + } + + &:hover, + &:focus { + --range-border-color: var(--range-active-border-color); + --range-thumb-color: var(--range-thumb-hover-color); + } + + &:active { + --range-thumb-color: var(--range-thumb-active-color); + + // Slider Thumb + &::-webkit-slider-thumb { + transform: scale(1.25); + } + + &::-moz-range-thumb { + transform: scale(1.25); + } + + &::-ms-thumb { + transform: scale(1.25); + } + } +} + +// Search +// :not() are needed to add Specificity and avoid !important on padding +input:not([type="checkbox"], [type="radio"], [type="range"], [type="file"]) { + &[type="search"] { + padding-inline-start: calc(var(--form-element-spacing-horizontal) + 1.75rem); + border-radius: 5rem; + background-image: var(--icon-search); + background-position: center left 1.125rem; + background-size: 1rem auto; + background-repeat: no-repeat; + + &[aria-invalid] { + @if $enable-important { + padding-inline-start: calc(var(--form-element-spacing-horizontal) + 1.75rem) !important; + } + @else { + padding-inline-start: calc(var(--form-element-spacing-horizontal) + 1.75rem); + } + background-position: center left 1.125rem, center right 0.75rem; + } + + &[aria-invalid="false"] { + background-image: var(--icon-search), var(--icon-valid); + } + + &[aria-invalid="true"] { + background-image: var(--icon-search), var(--icon-invalid); + } + } +} + +// Cancel button +[type="search"] { + &::-webkit-search-cancel-button { + -webkit-appearance: none; + display: none; + } +} + +[dir="rtl"] { + :where(input) { + &:not([type="checkbox"], [type="radio"], [type="range"], [type="file"]) { + &[type="search"] { + background-position: center right 1.125rem; + + &[aria-invalid] { + background-position: center right 1.125rem, center left 0.75rem; + } + } + } + } +} diff --git a/app/assets/stylesheets/content/_form-checkbox-radio.scss b/app/assets/stylesheets/content/_form-checkbox-radio.scss new file mode 100644 index 0000000..c83d35e --- /dev/null +++ b/app/assets/stylesheets/content/_form-checkbox-radio.scss @@ -0,0 +1,138 @@ +/** + * Form elements + * Checkboxes & Radios + */ + +[type="checkbox"], +[type="radio"] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + width: 1.25em; + height: 1.25em; + margin-top: -0.125em; + margin-right: 0.375em; + margin-left: 0; + margin-inline-start: 0; + margin-inline-end: 0.375em; + border-width: var(--border-width); + font-size: inherit; + vertical-align: middle; + cursor: pointer; + + &::-ms-check { + display: none; // unstyle IE checkboxes + } + + &:checked, + &:checked:active, + &:checked:focus { + --background-color: var(--primary); + --border-color: var(--primary); + background-image: var(--icon-checkbox); + background-position: center; + background-size: 0.75em auto; + background-repeat: no-repeat; + } + + & ~ label { + display: inline-block; + margin-right: 0.375em; + margin-bottom: 0; + cursor: pointer; + } +} + +// Checkboxes +[type="checkbox"] { + &:indeterminate { + --background-color: var(--primary); + --border-color: var(--primary); + background-image: var(--icon-minus); + background-position: center; + background-size: 0.75em auto; + background-repeat: no-repeat; + } +} + +// Radios +[type="radio"] { + border-radius: 50%; + + &:checked, + &:checked:active, + &:checked:focus { + --background-color: var(--primary-inverse); + border-width: 0.35em; + background-image: none; + } +} + +// Switchs +[type="checkbox"][role="switch"] { + --background-color: var(--switch-background-color); + --border-color: var(--switch-background-color); + --color: var(--switch-color); + + // Config + $switch-height: 1.25em; + $switch-width: 2.25em; + $switch-transition: 0.1s ease-in-out; + + // Styles + width: $switch-width; + height: $switch-height; + border: var(--border-width) solid var(--border-color); + border-radius: $switch-height; + background-color: var(--background-color); + line-height: $switch-height; + + &:focus { + --background-color: var(--switch-background-color); + --border-color: var(--switch-background-color); + } + + &:checked { + --background-color: var(--switch-checked-background-color); + --border-color: var(--switch-checked-background-color); + } + + &:before { + display: block; + width: calc(#{$switch-height} - (var(--border-width) * 2)); + height: 100%; + border-radius: 50%; + background-color: var(--color); + content: ""; + + @if $enable-transitions { + transition: margin $switch-transition; + } + } + + &:checked { + background-image: none; + + &::before { + margin-left: calc(#{$switch-width * 0.5} - var(--border-width)); + margin-inline-start: calc(#{$switch-width * 0.5} - var(--border-width)); + } + } +} + +// Aria-invalid +[type="checkbox"], +[type="checkbox"]:checked, +[type="radio"], +[type="radio"]:checked, +[type="checkbox"][role="switch"], +[type="checkbox"][role="switch"]:checked { + + &[aria-invalid="false"] { + --border-color: var(--form-element-valid-border-color); + } + + &[aria-invalid="true"] { + --border-color: var(--form-element-invalid-border-color); + } +}
\ No newline at end of file diff --git a/app/assets/stylesheets/content/_form.scss b/app/assets/stylesheets/content/_form.scss new file mode 100644 index 0000000..1dda8f3 --- /dev/null +++ b/app/assets/stylesheets/content/_form.scss @@ -0,0 +1,352 @@ +/** + * Form elements + */ + +// Reboot based on : +// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css +// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css +// –––––––––––––––––––– + +// 1. Change the font styles in all browsers +// 2. Remove the margin in Firefox and Safari +input, +optgroup, +select, +textarea { + margin: 0; // 2 + font-size: 1rem; // 1 + line-height: var(--line-height); // 1 + font-family: inherit; // 1 + letter-spacing: inherit; // 2 +} + +// Show the overflow in IE. +input { + overflow: visible; +} + +// Remove the inheritance of text transform in Edge, Firefox, and IE +select { + text-transform: none; +} + +// 1. Correct the text wrapping in Edge and IE +// 2. Correct the color inheritance from `fieldset` elements in IE +// 3. Remove the padding so developers are not caught out when they zero out +// `fieldset` elements in all browsers +legend { + max-width: 100%; // 1 + padding: 0; // 3 + color: inherit; // 2 + white-space: normal; // 1 +} + +// 1. Remove the default vertical scrollbar in IE +textarea { + overflow: auto; // 1 +} + +// Remove the padding in IE 10 +[type="checkbox"], +[type="radio"] { + padding: 0; +} + +// Correct the cursor style of increment and decrement buttons in Safari +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +// 1. Correct the odd appearance in Chrome and Safari +// 2. Correct the outline style in Safari +[type="search"] { + -webkit-appearance: textfield; // 1 + outline-offset: -2px; // 2 +} + +// Remove the inner padding in Chrome and Safari on macOS +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +// 1. Correct the inability to style clickable types in iOS and Safari +// 2. Change font properties to `inherit` in Safari +::-webkit-file-upload-button { + -webkit-appearance: button; // 1 + font: inherit; // 2 +} + +// Remove the inner border and padding of focus outlines in Firefox +::-moz-focus-inner { + padding: 0; + border-style: none; +} + +// Remove the focus outline in Firefox +:-moz-focusring { + outline: none; +} + +// Remove the additional :invalid styles in Firefox +:-moz-ui-invalid { + box-shadow: none; +} + +// Change the inconsistent appearance in IE (opinionated) +::-ms-expand { + display: none; +} + +// Remove the border and padding in all browsers (opinionated) +[type="file"], +[type="range"] { + padding: 0; + border-width: 0; +} + +// Pico +// –––––––––––––––––––– + +// Force height for alternatives input types +input:not([type="checkbox"], [type="radio"], [type="range"]) { + height: calc( + (1rem * var(--line-height)) + (var(--form-element-spacing-vertical) * 2) + + (var(--border-width) * 2) + ); +} + +// Fieldset +fieldset { + margin: 0; + margin-bottom: var(--spacing); + padding: 0; + border: 0; +} + +// Label & legend +label, +fieldset legend { + display: block; + margin-bottom: calc(var(--spacing) * 0.25); + font-weight: var(--form-label-font-weight, var(--font-weight)); +} + +// Blocks, 100% +input:not([type="checkbox"], [type="radio"]), +select, +textarea { + width: 100%; +} + +// Reset appearance (Not Checkboxes, Radios, Range and File) +input:not([type="checkbox"], [type="radio"], [type="range"], [type="file"]), +select, +textarea { + appearance: none; + padding: var(--form-element-spacing-vertical) + var(--form-element-spacing-horizontal); +} + +// Commons styles +input, +select, +textarea { + --background-color: var(--form-element-background-color); + --border-color: var(--form-element-border-color); + --color: var(--form-element-color); + --box-shadow: none; + border: var(--border-width) solid var(--border-color); + border-radius: var(--border-radius); + outline: none; + background-color: var(--background-color); + box-shadow: var(--box-shadow); + color: var(--color); + font-weight: var(--font-weight); + + @if $enable-transitions { + transition: background-color var(--transition), + border-color var(--transition), color var(--transition), + box-shadow var(--transition); + } +} + +// Active & Focus +input:not([type="submit"], [type="button"], [type="reset"], [type="checkbox"], [type="radio"], [readonly]), +:where(select, textarea) { + &:is(:active, :focus) { + --background-color: var(--form-element-active-background-color); + } +} + +// Active & Focus +input:not([type="submit"], [type="button"], [type="reset"], [role="switch"], [readonly]), +:where(select, textarea) { + &:is(:active, :focus) { + --border-color: var(--form-element-active-border-color); + } +} + +// Focus +input:not([type="submit"], [type="button"], [type="reset"], [type="range"], [type="file"], [readonly]), +select, +textarea { + &:focus { + --box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color); + } +} + +// Disabled +input:not([type="submit"], [type="button"], [type="reset"])[disabled], +select[disabled], +textarea[disabled], +:where(fieldset[disabled]) :is(input:not([type="submit"], [type="button"], [type="reset"]), select, textarea) { + --background-color: var(--form-element-disabled-background-color); + --border-color: var(--form-element-disabled-border-color); + opacity: var(--form-element-disabled-opacity); + pointer-events: none; +} + +// Aria-invalid +:where(input, select, textarea) { + &:not([type="checkbox"], [type="radio"], [type="date"], [type="datetime-local"], [type="month"], [type="time"], [type="week"]) { + &[aria-invalid] { + @if $enable-important { + padding-right: calc( + var(--form-element-spacing-horizontal) + 1.5rem + ) !important; + padding-left: var(--form-element-spacing-horizontal); + padding-inline-start: var(--form-element-spacing-horizontal) !important; + padding-inline-end: calc( + var(--form-element-spacing-horizontal) + 1.5rem + ) !important; + } + @else { + padding-right: calc(var(--form-element-spacing-horizontal) + 1.5rem); + padding-left: var(--form-element-spacing-horizontal); + padding-inline-start: var(--form-element-spacing-horizontal); + padding-inline-end: calc(var(--form-element-spacing-horizontal) + 1.5rem); + } + background-position: center right 0.75rem; + background-size: 1rem auto; + background-repeat: no-repeat; + } + + &[aria-invalid="false"] { + background-image: var(--icon-valid); + } + + &[aria-invalid="true"] { + background-image: var(--icon-invalid); + } + } + + &[aria-invalid="false"] { + --border-color: var(--form-element-valid-border-color); + + &:is(:active, :focus) { + @if $enable-important { + --border-color: var(--form-element-valid-active-border-color) !important; + --box-shadow: 0 0 0 var(--outline-width) var(--form-element-valid-focus-color) !important; + } + @else { + --border-color: var(--form-element-valid-active-border-color); + --box-shadow: 0 0 0 var(--outline-width) var(--form-element-valid-focus-color); + } + } + } + + &[aria-invalid="true"] { + --border-color: var(--form-element-invalid-border-color); + + &:is(:active, :focus) { + @if $enable-important { + --border-color: var(--form-element-invalid-active-border-color) !important; + --box-shadow: 0 0 0 var(--outline-width) var(--form-element-invalid-focus-color) !important; + } + @else { + --border-color: var(--form-element-invalid-active-border-color); + --box-shadow: 0 0 0 var(--outline-width) var(--form-element-invalid-focus-color); + } + } + } +} + +[dir="rtl"] { + :where(input, select, textarea) { + &:not([type="checkbox"], [type="radio"]) { + &:is([aria-invalid], [aria-invalid="true"], [aria-invalid="false"] ){ + background-position: center left 0.75rem; + } + } + } +} + +// Placeholder +input::placeholder, +input::-webkit-input-placeholder, +textarea::placeholder, +textarea::-webkit-input-placeholder, +select:invalid { + color: var(--form-element-placeholder-color); + opacity: 1; +} + +// Margin bottom (Not Checkboxes and Radios) +input:not([type="checkbox"], [type="radio"]), +select, +textarea { + margin-bottom: var(--spacing); +} + +// Select +select { + // Unstyle the caret on `<select>`s in IE10+. + &::-ms-expand { + border: 0; + background-color: transparent; + } + + &:not([multiple], [size]) { + padding-right: calc(var(--form-element-spacing-horizontal) + 1.5rem); + padding-left: var(--form-element-spacing-horizontal); + padding-inline-start: var(--form-element-spacing-horizontal); + padding-inline-end: calc(var(--form-element-spacing-horizontal) + 1.5rem); + background-image: var(--icon-chevron); + background-position: center right 0.75rem; + background-size: 1rem auto; + background-repeat: no-repeat; + } +} + +[dir="rtl"] { + select { + &:not([multiple], [size]) { + background-position: center left 0.75rem; + } + } +} + +// Helper +$inputs: "input, select, textarea"; + +@if ($enable-classes and $enable-grid) { + $inputs: $inputs + ", .grid"; +} + +:where(#{$inputs}) { + + small { + display: block; + width: 100%; + margin-top: calc(var(--spacing) * -0.75); + margin-bottom: var(--spacing); + color: var(--muted-color); + } +} + +// Styles for Input inside a label +label { + > :where(input, select, textarea) { + margin-top: calc(var(--spacing) * 0.25); + } +} diff --git a/app/assets/stylesheets/content/_miscs.scss b/app/assets/stylesheets/content/_miscs.scss new file mode 100644 index 0000000..526baba --- /dev/null +++ b/app/assets/stylesheets/content/_miscs.scss @@ -0,0 +1,33 @@ +/** + * Miscs + */ + +// Reboot based on : +// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css +// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css +// –––––––––––––––––––– + +// 1. Correct the inheritance of border color in Firefox +// 2. Add the correct box sizing in Firefox +hr { + height: 0; // 2 + border: 0; + border-top: 1px solid var(--muted-border-color); + color: inherit; // 1 +} + +// Add the correct display in IE 10+ +[hidden], +template { + @if $enable-important { + display: none !important; + } + @else { + display: none; + } +} + +// Add the correct display in IE 9- +canvas { + display: inline-block; +} diff --git a/app/assets/stylesheets/content/_table.scss b/app/assets/stylesheets/content/_table.scss new file mode 100644 index 0000000..d70cfd7 --- /dev/null +++ b/app/assets/stylesheets/content/_table.scss @@ -0,0 +1,50 @@ +/** + * Table + */ + +// Reboot based on : +// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css +// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css +// –––––––––––––––––––– + +// 1. Collapse border spacing in all browsers (opinionated) +// 2. Remove text indentation from table contents in Chrome, Edge, and Safari +:where(table) { + width: 100%; + border-collapse: collapse; // 1 + border-spacing: 0; + text-indent: 0; // 2 +} + +// Pico +// –––––––––––––––––––– + +// Cells +th, +td { + padding: calc(var(--spacing) / 2) var(--spacing); + border-bottom: var(--border-width) solid var(--table-border-color); + color: var(--color); + font-weight: var(--font-weight); + font-size: var(--font-size); + text-align: left; + text-align: start; +} + +// Footer +tfoot { + th, + td { + border-top: var(--border-width) solid var(--table-border-color); + border-bottom: 0; + } +} + +// Striped +table { + &[role="grid"] { + tbody tr:nth-child(odd) { + background-color: var(--table-row-stripped-background-color); + } + } +} diff --git a/app/assets/stylesheets/content/_typography.scss b/app/assets/stylesheets/content/_typography.scss new file mode 100644 index 0000000..a9ed71b --- /dev/null +++ b/app/assets/stylesheets/content/_typography.scss @@ -0,0 +1,264 @@ +/** + * Typography + */ + +// Reboot based on : +// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css +// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css +// –––––––––––––––––––– + +// Add the correct font weight in Chrome, Edge, and Safari +b, +strong { + font-weight: bolder; +} + +// Prevent `sub` and `sup` elements from affecting the line height in all browsers +sub, +sup { + position: relative; + font-size: 0.75em; + line-height: 0; + vertical-align: baseline; +} +sub { + bottom: -0.25em; +} +sup { + top: -0.5em; +} + +// Pico +// –––––––––––––––––––– + +address, +blockquote, +dl, +figure, +form, +ol, +p, +pre, +table, +ul { + margin-top: 0; + margin-bottom: var(--typography-spacing-vertical); + color: var(--color); + font-style: normal; + font-weight: var(--font-weight); + font-size: var(--font-size); +} + +// Links +// 1. Remove the gray background on active links in IE 10 +a, +[role="link"] { + --color: var(--primary); + --background-color: transparent; + outline: none; + background-color: var(--background-color); // 1 + color: var(--color); + text-decoration: var(--text-decoration); + + @if $enable-transitions { + transition: background-color var(--transition), color var(--transition), + text-decoration var(--transition), box-shadow var(--transition); + } + + &:is([aria-current], :hover, :active, :focus) { + --color: var(--primary-hover); + --text-decoration: underline; + } + + &:focus { + --background-color: var(--primary-focus); + } + + @if $enable-classes { + // Secondary + &.secondary { + --color: var(--secondary); + + &:is([aria-current], :hover, :active, :focus) { + --color: var(--secondary-hover); + } + + &:focus { + --background-color: var(--secondary-focus); + } + } + + // Contrast + &.contrast { + --color: var(--contrast); + + &:is([aria-current], :hover, :active, :focus) { + --color: var(--contrast-hover); + } + + &:focus { + --background-color: var(--contrast-focus); + } + } + } +} + +// Headings +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: 0; + margin-bottom: var(--typography-spacing-vertical); + color: var(--color); + font-weight: var(--font-weight); + font-size: var(--font-size); + font-family: var(--font-family); +} + +h1 { + --color: var(--h1-color); +} +h2 { + --color: var(--h2-color); +} +h3 { + --color: var(--h3-color); +} +h4 { + --color: var(--h4-color); +} +h5 { + --color: var(--h5-color); +} +h6 { + --color: var(--h6-color); +} + +// Margin-top for headings after a typography block +:where(address, blockquote, dl, figure, form, ol, p, pre, table, ul) { + ~ :is(h1, h2, h3, h4, h5, h6) { + margin-top: var(--typography-spacing-vertical); + } +} + +// Heading group +@if $enable-classes == false { + hgroup { + margin-bottom: var(--typography-spacing-vertical); + + > * { + margin-bottom: 0; + } + + > *:last-child { + --color: var(--muted-color); + --font-weight: unset; + font-size: 1rem; + font-family: unset; + } + } +} + +@if $enable-classes { + hgroup, + .headings { + margin-bottom: var(--typography-spacing-vertical); + + > * { + margin-bottom: 0; + } + + > *:last-child { + --color: var(--muted-color); + --font-weight: unset; + font-size: 1rem; + font-family: unset; + } + } +} + +// Paragraphs +p { + margin-bottom: var(--typography-spacing-vertical); +} + +// Small +small { + font-size: var(--font-size); +} + +// Lists +:where(dl, ol, ul) { + padding-right: 0; + padding-left: var(--spacing); + padding-inline-start: var(--spacing); + padding-inline-end: 0; + + li { + margin-bottom: calc(var(--typography-spacing-vertical) * 0.25); + } +} + +// Margin-top for nested lists +// 1. Remove the margin on nested lists in Chrome, Edge, IE, and Safari +:where(dl, ol, ul) { + :is(dl, ol, ul) { + margin: 0; // 1 + margin-top: calc(var(--typography-spacing-vertical) * 0.25); + } +} + +ul li { + list-style: square; +} + +// Highlighted text +mark { + padding: 0.125rem 0.25rem; + background-color: var(--mark-background-color); + color: var(--mark-color); + vertical-align: baseline; +} + +// Blockquote +blockquote { + display: block; + margin: var(--typography-spacing-vertical) 0; + padding: var(--spacing); + border-right: none; + border-left: 0.25rem solid var(--blockquote-border-color); + border-inline-start: 0.25rem solid var(--blockquote-border-color); + border-inline-end: none; + + footer { + margin-top: calc(var(--typography-spacing-vertical) * 0.5); + color: var(--blockquote-footer-color); + } +} + +// Abbreviations +// 1. Remove underline decoration in Chrome, Edge, IE, Opera, and Safari +abbr[title] { + border-bottom: 1px dotted; + text-decoration: none; // 1 + cursor: help; +} + +// Ins +ins { + color: var(--ins-color); + text-decoration: none; +} + +// del +del { + color: var(--del-color); +} + +// selection +::selection { + background-color: var(--primary-focus); +} diff --git a/app/assets/stylesheets/layout/_container.scss b/app/assets/stylesheets/layout/_container.scss new file mode 100644 index 0000000..6a20bea --- /dev/null +++ b/app/assets/stylesheets/layout/_container.scss @@ -0,0 +1,42 @@ +@if ($enable-class-container and $enable-classes) { + /** + * Container + */ + + .container, + .container-fluid { + width: 100%; + margin-right: auto; + margin-left: auto; + padding-right: var(--spacing); + padding-left: var(--spacing); + } + + .container { + @if map-get($breakpoints, "sm") { + @media (min-width: map-get($breakpoints, "sm")) { + max-width: map-get($viewports, "sm"); + padding-right: 0; + padding-left: 0; + } + } + + @if map-get($breakpoints, "md") { + @media (min-width: map-get($breakpoints, "md")) { + max-width: map-get($viewports, "md"); + } + } + + @if map-get($breakpoints, "lg") { + @media (min-width: map-get($breakpoints, "lg")) { + max-width: map-get($viewports, "lg"); + } + } + + @if map-get($breakpoints, "xl") { + @media (min-width: map-get($breakpoints, "xl")) { + max-width: map-get($viewports, "xl"); + } + } + } +} diff --git a/app/assets/stylesheets/layout/_document.scss b/app/assets/stylesheets/layout/_document.scss new file mode 100644 index 0000000..1607659 --- /dev/null +++ b/app/assets/stylesheets/layout/_document.scss @@ -0,0 +1,48 @@ +/** + * Document + * Content-box & Responsive typography + */ + +// Reboot based on : +// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css +// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css +// –––––––––––––––––––– + +// 1. Add border box sizing in all browsers (opinionated) +// 2. Backgrounds do not repeat by default (opinionated) +*, +*::before, +*::after { + box-sizing: border-box; // 1 + background-repeat: no-repeat; // 2 +} + +// 1. Add text decoration inheritance in all browsers (opinionated) +// 2. Add vertical alignment inheritance in all browsers (opinionated) +::before, +::after { + text-decoration: inherit; // 1 + vertical-align: inherit; // 2 +} + +// 1. Use the default cursor in all browsers (opinionated) +// 2. Change the line height in all browsers (opinionated) +// 3. Breaks words to prevent overflow in all browsers (opinionated) +// 4. Use a 4-space tab width in all browsers (opinionated) +// 5. Remove the grey highlight on links in iOS (opinionated) +// 6. Prevent adjustments of font size after orientation changes in iOS +:where(:root) { + -webkit-tap-highlight-color: transparent; // 5 + -webkit-text-size-adjust: 100%; // 6 + text-size-adjust: 100%; // 6 + background-color: var(--background-color); + color: var(--color); + font-weight: var(--font-weight); + font-size: var(--font-size); + line-height: var(--line-height); // 2 + font-family: var(--font-family); + text-rendering: optimizeLegibility; + overflow-wrap: break-word; // 3 + cursor: default; // 1 + tab-size: 4; // 4 +} diff --git a/app/assets/stylesheets/layout/_grid.scss b/app/assets/stylesheets/layout/_grid.scss new file mode 100644 index 0000000..572312b --- /dev/null +++ b/app/assets/stylesheets/layout/_grid.scss @@ -0,0 +1,24 @@ +@if ($enable-classes and $enable-grid) { + /** + * Grid + * Minimal grid system with auto-layout columns + */ + + .grid { + grid-column-gap: var(--grid-spacing-horizontal); + grid-row-gap: var(--grid-spacing-vertical); + display: grid; + grid-template-columns: 1fr; + margin: 0; + + @if map-get($breakpoints, "lg") { + @media (min-width: map-get($breakpoints, "lg")) { + grid-template-columns: repeat(auto-fit, minmax(0%, 1fr)); + } + } + + & > * { + min-width: 0; // HACK for childs in overflow + } + } +} diff --git a/app/assets/stylesheets/layout/_scroller.scss b/app/assets/stylesheets/layout/_scroller.scss new file mode 100644 index 0000000..9b58ef7 --- /dev/null +++ b/app/assets/stylesheets/layout/_scroller.scss @@ -0,0 +1,16 @@ +/** + * Horizontal scroller (<figure>) + */ + +// Wrapper to make any content responsive across all viewports +figure { + display: block; + margin: 0; + padding: 0; + overflow-x: auto; + + figcaption { + padding: calc(var(--spacing) * 0.5) 0; + color: var(--muted-color); + } +} diff --git a/app/assets/stylesheets/layout/_section.scss b/app/assets/stylesheets/layout/_section.scss new file mode 100644 index 0000000..8bc6902 --- /dev/null +++ b/app/assets/stylesheets/layout/_section.scss @@ -0,0 +1,8 @@ +/** + * Section + * Responsive spacings for section + */ + +section { + margin-bottom: var(--block-spacing-vertical); +} diff --git a/app/assets/stylesheets/layout/_sectioning.scss b/app/assets/stylesheets/layout/_sectioning.scss new file mode 100644 index 0000000..00d73c5 --- /dev/null +++ b/app/assets/stylesheets/layout/_sectioning.scss @@ -0,0 +1,70 @@ +/** + * Sectioning + * Container and responsive spacings for header, main, footer + */ + +// Reboot based on : +// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css +// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css +// –––––––––––––––––––– + +// Render the `main` element consistently in IE +main { + display: block; +} + +// Pico +// –––––––––––––––––––– + +// 1. Remove the margin in all browsers (opinionated) +#{$semantic-root-element} { + width: 100%; + margin: 0; // 1 + + > header, + > main, + > footer { + width: 100%; + margin-right: auto; + margin-left: auto; + + // Semantic container + @if $enable-semantic-container { + padding: var(--block-spacing-vertical) var(--block-spacing-horizontal); + + // Centered viewport + @if $enable-viewport { + @if map-get($breakpoints, "sm") and $enable-viewport { + @media (min-width: map-get($breakpoints, "sm")) { + max-width: map-get($viewports, "sm"); + padding-right: 0; + padding-left: 0; + } + } + + @if map-get($breakpoints, "md") and $enable-viewport { + @media (min-width: map-get($breakpoints, "md")) { + max-width: map-get($viewports, "md"); + } + } + + @if map-get($breakpoints, "lg") and $enable-viewport { + @media (min-width: map-get($breakpoints, "lg")) { + max-width: map-get($viewports, "lg"); + } + } + + @if map-get($breakpoints, "xl") and $enable-viewport { + @media (min-width: map-get($breakpoints, "xl")) { + max-width: map-get($viewports, "xl"); + } + } + } + } + + // Semantic container + @else { + padding: var(--block-spacing-vertical) 0; + } + } +} diff --git a/app/assets/stylesheets/pico.classless.scss b/app/assets/stylesheets/pico.classless.scss new file mode 100644 index 0000000..c5ea832 --- /dev/null +++ b/app/assets/stylesheets/pico.classless.scss @@ -0,0 +1,13 @@ +// Config +// -------------------- + +// Enable <header>, <main>, <footer> inside $semantic-root-element as containers +$enable-semantic-container: true; + +// Enable .classes +$enable-classes: false; + +// Pico Lib +// -------------------- + +@import "pico"; diff --git a/app/assets/stylesheets/pico.fluid.classless.scss b/app/assets/stylesheets/pico.fluid.classless.scss new file mode 100644 index 0000000..ef6af04 --- /dev/null +++ b/app/assets/stylesheets/pico.fluid.classless.scss @@ -0,0 +1,16 @@ +// Config +// -------------------- + +// Enable <header>, <main>, <footer> inside $semantic-root-element as containers +$enable-semantic-container: true; + +// Enable a centered viewport for <header>, <main>, <footer> inside $enable-semantic-container +$enable-viewport: false; + +// Enable .classes +$enable-classes: false; + +// Pico Lib +// -------------------- + +@import "pico"; diff --git a/app/assets/stylesheets/pico.scss b/app/assets/stylesheets/pico.scss new file mode 100644 index 0000000..b248799 --- /dev/null +++ b/app/assets/stylesheets/pico.scss @@ -0,0 +1,43 @@ +/*! + * Pico CSS v1.5.11 (https://picocss.com) + * Copyright 2019-2023 - Licensed under MIT + */ + +// Config +@import "variables"; + +// Theming +@import "themes/default"; + +// Layout +@import "layout/document"; // html +@import "layout/sectioning"; // body, header, main, footer +@import "layout/container"; // .container, .container-fluid +@import "layout/section"; // section +@import "layout/grid"; // .grid +@import "layout/scroller"; // figure + +// Content +@import "content/typography"; // a, headings, p, ul, blockquote, ... +@import "content/embedded"; // audio, canvas, iframe, img, svg, video +@import "content/button"; // button, a[role=button], type=button, type=submit ... +@import "content/form"; // input, select, textarea, label, fieldset, legend +@import "content/form-checkbox-radio"; // type=checkbox, type=radio, role=switch +@import "content/form-alt-input-types"; // type=color, type=date, type=file, type=search, ... +@import "content/table"; // table, tr, td, ... +@import "content/code"; // pre, code, ... +@import "content/miscs"; // hr, template, [hidden], dialog, canvas + +// Components +@import "components/accordion"; // details, summary +@import "components/card"; // article +@import "components/modal"; // dialog +@import "components/nav"; // nav +@import "components/progress"; // progress +@import "components/dropdown"; // dropdown + +// Utilities +@import "utilities/loading"; // aria-busy=true +@import "utilities/tooltip"; // data-tooltip +@import "utilities/accessibility"; // -ms-touch-action, aria-* +@import "utilities/reduce-motion"; // prefers-reduced-motion diff --git a/app/assets/stylesheets/pico.slim.scss b/app/assets/stylesheets/pico.slim.scss new file mode 100644 index 0000000..cb9aa42 --- /dev/null +++ b/app/assets/stylesheets/pico.slim.scss @@ -0,0 +1,47 @@ +/*! + * Pico CSS v1.5.11 (https://picocss.com) + * Copyright 2019-2023 - Licensed under MIT + * + * Slim version example + * You can export only the modules you need + */ + +// Config +// -------------------- + +// Enable responsive spacings for <header>, <main>, <footer>, <section>, <article> +$enable-responsive-spacings: false; + +// Enable transitions +$enable-transitions: false; + +// Enable overriding with !important +$enable-important: false; + +// Pico Lib +// -------------------- + +// Config +@import "variables"; + +// Theming +@import "themes/default"; + +// Layout +@import "layout/document"; // html +@import "layout/sectioning"; // body, header, main, footer +@import "layout/container"; // .container, .container-fluid +@import "layout/section"; // section +@import "layout/grid"; // .grid +@import "layout/scroller"; // figure + +// Content +@import "content/typography"; // a, headings, p, ul, blockquote, ... +@import "content/embedded"; // audio, canvas, iframe, img, svg, video +@import "content/button"; // button, a[role=button], type=button, type=submit ... +@import "content/form"; // input, select, textarea, label, fieldset, legend +@import "content/table"; // table, tr, td, ... + +// Utilities +@import "utilities/accessibility"; // -ms-touch-action, aria-* +@import "utilities/reduce-motion"; // prefers-reduced-motion diff --git a/app/assets/stylesheets/postcss.config.js b/app/assets/stylesheets/postcss.config.js new file mode 100644 index 0000000..4eaff47 --- /dev/null +++ b/app/assets/stylesheets/postcss.config.js @@ -0,0 +1,9 @@ +module.exports = { + syntax: "postcss-scss", + map: false, + plugins: { + "css-declaration-sorter": { + order: "smacss" + } + } +}
\ No newline at end of file diff --git a/app/assets/stylesheets/themes/default.scss b/app/assets/stylesheets/themes/default.scss new file mode 100644 index 0000000..c96b7e7 --- /dev/null +++ b/app/assets/stylesheets/themes/default.scss @@ -0,0 +1,37 @@ +/** + * Theme: default + */ + +// Variables +@import "../variables"; +@import "default/colors"; + +// Commons styles +@import "default/styles"; + +// Light theme (Default) +// Can be forced with data-theme="light" +@import "default/light"; + +// Dark theme (Auto) +// Automatically enabled if user has Dark mode enabled +@import "default/dark"; +@media only screen and (prefers-color-scheme: dark) { + :root:not([data-theme]) { + @include dark; + } +} + +// Dark theme (Forced) +// Enabled if forced with data-theme="dark" +[data-theme="dark"] { + @include dark; +} + +// Accent-color +progress, +[type="checkbox"], +[type="radio"], +[type="range"] { + accent-color: var(--primary); +} diff --git a/app/assets/stylesheets/themes/default/_colors.scss b/app/assets/stylesheets/themes/default/_colors.scss new file mode 100644 index 0000000..19079ff --- /dev/null +++ b/app/assets/stylesheets/themes/default/_colors.scss @@ -0,0 +1,65 @@ +// Navy-Grey +$grey-hue: 205 !default; +$grey-50: hsl($grey-hue, 20%, 94%) !default; +$grey-100: hsl($grey-hue, 18%, 86%) !default; +$grey-200: hsl($grey-hue, 16%, 77%) !default; +$grey-300: hsl($grey-hue, 14%, 68%) !default; +$grey-400: hsl($grey-hue, 12%, 59%) !default; +$grey-500: hsl($grey-hue, 10%, 50%) !default; +$grey-600: hsl($grey-hue, 15%, 41%) !default; +$grey-700: hsl($grey-hue, 20%, 32%) !default; +$grey-800: hsl($grey-hue, 25%, 23%) !default; +$grey-900: hsl($grey-hue, 30%, 15%) !default; + +// Light Blue +$primary-hue: 195 !default; +$primary-50: hsl($primary-hue, 90%, 94%) !default; +$primary-100: hsl($primary-hue, 88%, 86%) !default; +$primary-200: hsl($primary-hue, 86%, 77%) !default; +$primary-300: hsl($primary-hue, 84%, 68%) !default; +$primary-400: hsl($primary-hue, 82%, 59%) !default; +$primary-500: hsl($primary-hue, 80%, 50%) !default; +$primary-600: hsl($primary-hue, 85%, 41%) !default; +$primary-700: hsl($primary-hue, 90%, 32%) !default; +$primary-800: hsl($primary-hue, 95%, 23%) !default; +$primary-900: hsl($primary-hue, 100%, 15%) !default; + +// Black & White +$black: #000 !default; +$white: #fff !default; + +// Amber +$amber-50: #fff8e1 !default; +$amber-100: #ffecb3 !default; +$amber-200: #ffe082 !default; +$amber-300: #ffd54f !default; +$amber-400: #ffca28 !default; +$amber-500: #ffc107 !default; +$amber-600: #ffb300 !default; +$amber-700: #ffa000 !default; +$amber-800: #ff8f00 !default; +$amber-900: #ff6f00 !default; + +// Green +$green-50: #e8f5e9 !default; +$green-100: #c8e6c9 !default; +$green-200: #a5d6a7 !default; +$green-300: #81c784 !default; +$green-400: #66bb6a !default; +$green-500: #4caf50 !default; +$green-600: #43a047 !default; +$green-700: #388e3c !default; +$green-800: #2e7d32 !default; +$green-900: #1b5e20 !default; + +// Red +$red-50: #ffebee !default; +$red-100: #ffcdd2 !default; +$red-200: #ef9a9a !default; +$red-300: #e57373 !default; +$red-400: #ef5350 !default; +$red-500: #f44336 !default; +$red-600: #e53935 !default; +$red-700: #d32f2f !default; +$red-800: #c62828 !default; +$red-900: #b71c1c !default; diff --git a/app/assets/stylesheets/themes/default/_dark.scss b/app/assets/stylesheets/themes/default/_dark.scss new file mode 100644 index 0000000..2bf12e2 --- /dev/null +++ b/app/assets/stylesheets/themes/default/_dark.scss @@ -0,0 +1,159 @@ +@import "../../functions"; + +// Default: Dark theme +@mixin dark { + --background-color: #{mix($black, $grey-900, 37.5%)}; + + // Texts colors + --color: #{$grey-200}; + --h1-color: #{$grey-50}; + --h2-color: #{mix($grey-100, $grey-50)}; + --h3-color: #{$grey-100}; + --h4-color: #{mix($grey-200, $grey-100)}; + --h5-color: #{$grey-200}; + --h6-color: #{mix($grey-300, $grey-200)}; + + // Muted colors + --muted-color: #{$grey-500}; + --muted-border-color: #{mix($grey-900, $grey-800, 75%)}; + + // Primary colors + --primary: #{$primary-600}; + --primary-hover: #{$primary-500}; + --primary-focus: #{rgba($primary-600, 0.25)}; + --primary-inverse: #{$white}; + + // Secondary colors + --secondary: #{$grey-600}; + --secondary-hover: #{$grey-500}; + --secondary-focus: #{rgba($grey-500, 0.25)}; + --secondary-inverse: #{$white}; + + // Contrast colors + --contrast: #{$grey-50}; + --contrast-hover: #{$white}; + --contrast-focus: #{rgba($grey-500, 0.25)}; + --contrast-inverse: #{$black}; + + // Highlighted text (<mark>) + --mark-background-color: #{mix($grey-300, $amber-300)}; + --mark-color: #{mix($black, $grey-900, 37.5%)}; + + // Inserted (<ins>) & Deleted (<ins>) + --ins-color: #{$green-700}; + --del-color: #{$red-800}; + + // Blockquote + --blockquote-border-color: var(--muted-border-color); + --blockquote-footer-color: var(--muted-color); + + // Button + // To disable box-shadow, remove the var or set to '0 0 0 rgba(0, 0, 0, 0)' + // Don't use, 'none, 'false, 'null', '0', etc. + --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + + // Form elements + --form-element-background-color: #{mix($black, $grey-900, 37.5%)}; + --form-element-border-color: #{mix($grey-800, $grey-700)}; + --form-element-color: var(--color); + --form-element-placeholder-color: var(--muted-color); + --form-element-active-background-color: var(--form-element-background-color); + --form-element-active-border-color: var(--primary); + --form-element-focus-color: var(--primary-focus); + --form-element-disabled-background-color: #{$grey-800}; + --form-element-disabled-border-color: #{$grey-700}; + --form-element-disabled-opacity: 0.5; + --form-element-invalid-border-color: #{$red-900}; + --form-element-invalid-active-border-color: #{$red-800}; + --form-element-invalid-focus-color: #{rgba($red-800, 0.25)}; + --form-element-valid-border-color: #{$green-800}; + --form-element-valid-active-border-color: #{$green-700}; + --form-element-valid-focus-color: #{rgba($green-700, 0.25)}; + + // Switch (input[type="checkbox"][role="switch"]) + --switch-background-color: #{mix($grey-800, $grey-700)}; + --switch-color: var(--primary-inverse); + --switch-checked-background-color: var(--primary); + + // Range (input[type="range"]) + --range-border-color: #{mix($grey-900, $grey-800)}; + --range-active-border-color: #{$grey-800}; + --range-thumb-border-color: var(--background-color); + --range-thumb-color: var(--secondary); + --range-thumb-hover-color: var(--secondary-hover); + --range-thumb-active-color: var(--primary); + + // Table + --table-border-color: var(--muted-border-color); + --table-row-stripped-background-color: #{rgba($grey-500, 0.05)}; + + // Code + --code-background-color: #{mix($black, $grey-900, 12.5%)}; + --code-color: var(--muted-color); + --code-kbd-background-color: var(--contrast); + --code-kbd-color: var(--contrast-inverse); + --code-tag-color: #{hsl(330, 30%, 50%)}; + --code-property-color: #{hsl(185, 30%, 50%)}; + --code-value-color: #{hsl(40, 10%, 50%)}; + --code-comment-color: #{mix($grey-700, $grey-600)}; + + // Accordion (<details>) + --accordion-border-color: var(--muted-border-color); + --accordion-active-summary-color: var(--primary); + --accordion-close-summary-color: var(--color); + --accordion-open-summary-color: var(--muted-color); + + // Card (<article>) + $box-shadow-elevation: 1rem; + $box-shadow-blur-strengh: 6rem; + $box-shadow-opacity: 0.06; + --card-background-color: #{mix($black, $grey-900, 25%)}; + --card-border-color: var(--card-background-color); + --card-box-shadow: + #{($box-shadow-elevation * 0.5 * 0.029)} #{($box-shadow-elevation * 0.029)} #{($box-shadow-blur-strengh * 0.029)} #{rgba($black, ($box-shadow-opacity * 0.283))}, + #{($box-shadow-elevation * 0.5 * 0.067)} #{($box-shadow-elevation * 0.067)} #{($box-shadow-blur-strengh * 0.067)} #{rgba($black, ($box-shadow-opacity * 0.4))}, + #{($box-shadow-elevation * 0.5 * 0.125)} #{($box-shadow-elevation * 0.125)} #{($box-shadow-blur-strengh * 0.125)} #{rgba($black, ($box-shadow-opacity * 0.5))}, + #{($box-shadow-elevation * 0.5 * 0.225)} #{($box-shadow-elevation * 0.225)} #{($box-shadow-blur-strengh * 0.225)} #{rgba($black, ($box-shadow-opacity * 0.6))}, + #{($box-shadow-elevation * 0.5 * 0.417)} #{($box-shadow-elevation * 0.417)} #{($box-shadow-blur-strengh * 0.417)} #{rgba($black, ($box-shadow-opacity * 0.717))}, + #{($box-shadow-elevation * 0.5)} #{$box-shadow-elevation} #{$box-shadow-blur-strengh} #{rgba($black, $box-shadow-opacity)}, + 0 0 0 0.0625rem #{rgba($black, ($box-shadow-opacity * 0.25) )}; + --card-sectionning-background-color: #{mix($black, $grey-900, 12.5%)}; + + // Dropdown (<details role="list">) + --dropdown-background-color: #{$grey-900}; + --dropdown-border-color: #{mix($grey-900, $grey-800)}; + --dropdown-box-shadow: var(--card-box-shadow); + --dropdown-color: var(--color); + --dropdown-hover-background-color: #{rgba(mix($grey-900, $grey-800), 0.75)}; + + // Modal (<dialog>) + --modal-overlay-background-color: #{rgba(mix($grey-900, $grey-800), 0.8)}; + + // Progress + --progress-background-color: #{mix($grey-900, $grey-800)}; + --progress-color: var(--primary); + + // Loading ([aria-busy=true]) + --loading-spinner-opacity: 0.5; + + // Tooltip ([data-tooltip]) + --tooltip-background-color: var(--contrast); + --tooltip-color: var(--contrast-inverse); + + // Icons + --icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($white)}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); + --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-300)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + --icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($white)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + --icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($black)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-500)}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E"); + --icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-300)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E"); + --icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($red-900)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); + --icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($white)}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E"); + --icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-300)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E"); + --icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-300)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E"); + --icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($green-800)}' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); + + // Document + color-scheme: dark; +} diff --git a/app/assets/stylesheets/themes/default/_light.scss b/app/assets/stylesheets/themes/default/_light.scss new file mode 100644 index 0000000..237fb8f --- /dev/null +++ b/app/assets/stylesheets/themes/default/_light.scss @@ -0,0 +1,159 @@ +@import "../../functions"; + +// Default: Light theme +[data-theme="light"], +:root:not([data-theme="dark"]) { + --background-color: #{$white}; + + // Texts colors + --color: #{$grey-700}; + --h1-color: #{$grey-900}; + --h2-color: #{mix($grey-900, $grey-800)}; + --h3-color: #{$grey-800}; + --h4-color: #{mix($grey-800, $grey-700)}; + --h5-color: #{$grey-700}; + --h6-color: #{mix($grey-700, $grey-600)}; + + // Muted colors + --muted-color: #{$grey-500}; + --muted-border-color: #{$grey-50}; + + // Primary colors + --primary: #{$primary-600}; + --primary-hover: #{$primary-700}; + --primary-focus: #{rgba($primary-600, 0.125)}; + --primary-inverse: #{$white}; + + // Secondary colors + --secondary: #{$grey-600}; + --secondary-hover: #{$grey-700}; + --secondary-focus: #{rgba($grey-600, 0.125)}; + --secondary-inverse: #{$white}; + + // Contrast colors + --contrast: #{$grey-900}; + --contrast-hover: #{$black}; + --contrast-focus: #{rgba($grey-600, 0.125)}; + --contrast-inverse: #{$white}; + + // Highlighted text (<mark>) + --mark-background-color: #{mix($amber-100, $amber-50)}; + --mark-color: #{mix($grey-900, $amber-900, 75%)}; + + // Inserted (<ins>) & Deleted (<ins>) + --ins-color: #{$green-700}; + --del-color: #{$red-800}; + + // Blockquote + --blockquote-border-color: var(--muted-border-color); + --blockquote-footer-color: var(--muted-color); + + // Button + // To disable box-shadow, remove the var or set to '0 0 0 rgba(0, 0, 0, 0)' + // Don't use, 'none, 'false, 'null', '0', etc. + --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + + // Form elements + --form-element-background-color: transparent; + --form-element-border-color: #{$grey-300}; + --form-element-color: var(--color); + --form-element-placeholder-color: var(--muted-color); + --form-element-active-background-color: transparent; + --form-element-active-border-color: var(--primary); + --form-element-focus-color: var(--primary-focus); + --form-element-disabled-background-color: #{$grey-100}; + --form-element-disabled-border-color: #{$grey-300}; + --form-element-disabled-opacity: 0.5; + --form-element-invalid-border-color: #{$red-800}; + --form-element-invalid-active-border-color: #{$red-700}; + --form-element-invalid-focus-color: #{rgba($red-700, 0.125)}; + --form-element-valid-border-color: #{$green-700}; + --form-element-valid-active-border-color: #{$green-600}; + --form-element-valid-focus-color: #{rgba($green-600, 0.125)}; + + // Switch (input[type="checkbox"][role="switch"]) + --switch-background-color: #{$grey-200}; + --switch-color: var(--primary-inverse); + --switch-checked-background-color: var(--primary); + + // Range (input[type="range"]) + --range-border-color: #{$grey-100}; + --range-active-border-color: #{$grey-200}; + --range-thumb-border-color: var(--background-color); + --range-thumb-color: var(--secondary); + --range-thumb-hover-color: var(--secondary-hover); + --range-thumb-active-color: var(--primary); + + // Table + --table-border-color: var(--muted-border-color); + --table-row-stripped-background-color: #{mix($grey-50, $white)}; + + // Code + --code-background-color: #{$grey-50}; + --code-color: var(--muted-color); + --code-kbd-background-color: var(--contrast); + --code-kbd-color: var(--contrast-inverse); + --code-tag-color: #{hsl(330, 40%, 50%)}; + --code-property-color: #{hsl(185, 40%, 40%)}; + --code-value-color: #{hsl(40, 20%, 50%)}; + --code-comment-color: #{$grey-300}; + + // Accordion (<details>) + --accordion-border-color: var(--muted-border-color); + --accordion-close-summary-color: var(--color); + --accordion-open-summary-color: var(--muted-color); + + // Card (<article>) + $box-shadow-elevation: 1rem; + $box-shadow-blur-strengh: 6rem; + $box-shadow-opacity: 0.06; + --card-background-color: var(--background-color); + --card-border-color: var(--muted-border-color); + --card-box-shadow: + #{($box-shadow-elevation * 0.5 * 0.029)} #{($box-shadow-elevation * 0.029)} #{($box-shadow-blur-strengh * 0.029)} #{rgba($grey-900, ($box-shadow-opacity * 0.283))}, + #{($box-shadow-elevation * 0.5 * 0.067)} #{($box-shadow-elevation * 0.067)} #{($box-shadow-blur-strengh * 0.067)} #{rgba($grey-900, ($box-shadow-opacity * 0.4))}, + #{($box-shadow-elevation * 0.5 * 0.125)} #{($box-shadow-elevation * 0.125)} #{($box-shadow-blur-strengh * 0.125)} #{rgba($grey-900, ($box-shadow-opacity * 0.5))}, + #{($box-shadow-elevation * 0.5 * 0.225)} #{($box-shadow-elevation * 0.225)} #{($box-shadow-blur-strengh * 0.225)} #{rgba($grey-900, ($box-shadow-opacity * 0.6))}, + #{($box-shadow-elevation * 0.5 * 0.417)} #{($box-shadow-elevation * 0.417)} #{($box-shadow-blur-strengh * 0.417)} #{rgba($grey-900, ($box-shadow-opacity * 0.717))}, + #{($box-shadow-elevation * 0.5)} #{$box-shadow-elevation} #{$box-shadow-blur-strengh} #{rgba($grey-900, $box-shadow-opacity)}, + 0 0 0 0.0625rem #{rgba($grey-900, ($box-shadow-opacity * 0.25) )}; + --card-sectionning-background-color: #{mix($grey-50, $white, 25%)}; + + // Dropdown (<details role="list">) + --dropdown-background-color: #{mix($grey-50, $white, 25%)}; + --dropdown-border-color: #{mix($grey-100, $grey-50)}; + --dropdown-box-shadow: var(--card-box-shadow); + --dropdown-color: var(--color); + --dropdown-hover-background-color: #{$grey-50}; + + // Modal (<dialog>) + --modal-overlay-background-color: #{rgba($grey-100, 0.7)}; + + // Progress + --progress-background-color: #{$grey-100}; + --progress-color: var(--primary); + + // Loading ([aria-busy=true]) + --loading-spinner-opacity: 0.5; + + // Tooltip ([data-tooltip]) + --tooltip-background-color: var(--contrast); + --tooltip-color: var(--contrast-inverse); + + // Icons + --icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($white)}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); + --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-700)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + --icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($white)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + --icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($white)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-500)}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E"); + --icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-700)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E"); + --icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($red-800)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); + --icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($white)}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E"); + --icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-700)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E"); + --icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($grey-700)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E"); + --icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{to-rgb($green-700)}' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); + + // Document + color-scheme: light; +} diff --git a/app/assets/stylesheets/themes/default/_styles.scss b/app/assets/stylesheets/themes/default/_styles.scss new file mode 100644 index 0000000..3a0a46d --- /dev/null +++ b/app/assets/stylesheets/themes/default/_styles.scss @@ -0,0 +1,247 @@ +// Commons Styles +:root { + // Typography + --font-family: system-ui, -apple-system, "Segoe UI", "Roboto", "Ubuntu", + "Cantarell", "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol", "Noto Color Emoji"; + --line-height: 1.5; + --font-weight: 400; + --font-size: 16px; + + // Responsive typography + @if $enable-responsive-typography { + @if map-get($breakpoints, "sm") { + @media (min-width: map-get($breakpoints, "sm")) { + --font-size: 17px; + } + } + + @if map-get($breakpoints, "md") { + @media (min-width: map-get($breakpoints, "md")) { + --font-size: 18px; + } + } + + @if map-get($breakpoints, "lg") { + @media (min-width: map-get($breakpoints, "lg")) { + --font-size: 19px; + } + } + + @if map-get($breakpoints, "xl") { + @media (min-width: map-get($breakpoints, "xl")) { + --font-size: 20px; + } + } + } + + // Borders + --border-radius: 0.25rem; + --border-width: 1px; + --outline-width: 3px; + + // Spacings + --spacing: 1rem; + + // Spacings for typography elements + --typography-spacing-vertical: 1.5rem; + + // Spacings for body > header, body > main, body > footer, section, article + --block-spacing-vertical: calc(var(--spacing) * 2); + --block-spacing-horizontal: var(--spacing); + + @if ($enable-classes and $enable-grid) { + --grid-spacing-vertical: 0; + --grid-spacing-horizontal: var(--spacing); + } + + // Spacings for form elements and button + --form-element-spacing-vertical: 0.75rem; + --form-element-spacing-horizontal: 1rem; + + // Spacings for nav component + --nav-element-spacing-vertical: 1rem; + --nav-element-spacing-horizontal: 0.5rem; + --nav-link-spacing-vertical: 0.5rem; + --nav-link-spacing-horizontal: 0.5rem; + + // Font weight for form labels & fieldsets legend + --form-label-font-weight: var(--font-weight); + + // Transitions + --transition: 0.2s ease-in-out; + + // Modal (<dialog>) + --modal-overlay-backdrop-filter: blur(0.25rem); +} + +// Responsives spacings +@if $enable-responsive-spacings { + // Sectioning + #{$semantic-root-element} > header, + #{$semantic-root-element} > main, + #{$semantic-root-element} > footer, + section { + @if map-get($breakpoints, "sm") { + @media (min-width: map-get($breakpoints, "sm")) { + --block-spacing-vertical: calc(var(--spacing) * 2.5); + } + } + + @if map-get($breakpoints, "md") { + @media (min-width: map-get($breakpoints, "md")) { + --block-spacing-vertical: calc(var(--spacing) * 3); + } + } + + @if map-get($breakpoints, "lg") { + @media (min-width: map-get($breakpoints, "lg")) { + --block-spacing-vertical: calc(var(--spacing) * 3.5); + } + } + + @if map-get($breakpoints, "xl") { + @media (min-width: map-get($breakpoints, "xl")) { + --block-spacing-vertical: calc(var(--spacing) * 4); + } + } + } + + // Card (<article>) + article { + @if map-get($breakpoints, "sm") { + @media (min-width: map-get($breakpoints, "sm")) { + --block-spacing-horizontal: calc(var(--spacing) * 1.25); + } + } + + @if map-get($breakpoints, "md") { + @media (min-width: map-get($breakpoints, "md")) { + --block-spacing-horizontal: calc(var(--spacing) * 1.5); + } + } + + @if map-get($breakpoints, "lg") { + @media (min-width: map-get($breakpoints, "lg")) { + --block-spacing-horizontal: calc(var(--spacing) * 1.75); + } + } + + @if map-get($breakpoints, "xl") { + @media (min-width: map-get($breakpoints, "xl")) { + --block-spacing-horizontal: calc(var(--spacing) * 2); + } + } + } + + // Modal + dialog > article { + + --block-spacing-vertical: calc(var(--spacing) * 2); + --block-spacing-horizontal: var(--spacing); + + @if map-get($breakpoints, "sm") { + @media (min-width: map-get($breakpoints, "sm")) { + --block-spacing-vertical: calc(var(--spacing) * 2.5); + --block-spacing-horizontal: calc(var(--spacing) * 1.25); + } + } + + @if map-get($breakpoints, "md") { + @media (min-width: map-get($breakpoints, "md")) { + --block-spacing-vertical: calc(var(--spacing) * 3); + --block-spacing-horizontal: calc(var(--spacing) * 1.5); + } + } + } +} + +// Link +a { + --text-decoration: none; + + // Secondary & Contrast + @if $enable-classes { + &.secondary, + &.contrast { + --text-decoration: underline; + } + } +} + +// Small +small { + --font-size: 0.875em; +} + +// Headings +h1, +h2, +h3, +h4, +h5, +h6 { + --font-weight: 700; +} + +h1 { + --font-size: 2rem; + --typography-spacing-vertical: 3rem; +} + +h2 { + --font-size: 1.75rem; + --typography-spacing-vertical: 2.625rem; +} + +h3 { + --font-size: 1.5rem; + --typography-spacing-vertical: 2.25rem; +} + +h4 { + --font-size: 1.25rem; + --typography-spacing-vertical: 1.874rem; +} + +h5 { + --font-size: 1.125rem; + --typography-spacing-vertical: 1.6875rem; +} + +// Forms elements +[type="checkbox"], +[type="radio"] { + --border-width: 2px; +} + +[type="checkbox"][role="switch"] { + --border-width: 3px; +} + +// Table +thead, +tfoot { + th, + td { + --border-width: 3px; + } +} + +:not(thead, tfoot) > * > td { + --font-size: 0.875em; +} + +// Code +pre, +code, +kbd, +samp { + --font-family: "Menlo", "Consolas", "Roboto Mono", "Ubuntu Monospace", + "Noto Mono", "Oxygen Mono", "Liberation Mono", monospace, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} + +kbd { + --font-weight: bolder; +} diff --git a/app/assets/stylesheets/utilities/_accessibility.scss b/app/assets/stylesheets/utilities/_accessibility.scss new file mode 100644 index 0000000..e97ed7e --- /dev/null +++ b/app/assets/stylesheets/utilities/_accessibility.scss @@ -0,0 +1,52 @@ +/** + * Accessibility & User interaction + */ + +// Based on : +// - normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css +// - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css +// –––––––––––––––––––– + +// Accessibility + +// Change the cursor on control elements in all browsers (opinionated) +[aria-controls] { + cursor: pointer; +} + +// Change the cursor on disabled, not-editable, or otherwise inoperable elements in all browsers (opinionated) +[aria-disabled="true"], +[disabled] { + cursor: not-allowed; +} + +// Change the display on visually hidden accessible elements in all browsers (opinionated) +[aria-hidden="false"][hidden] { + display: initial; +} + +[aria-hidden="false"][hidden]:not(:focus) { + clip: rect(0, 0, 0, 0); + position: absolute; +} + +// User interaction +// Remove the tapping delay in IE 10 +a, +area, +button, +input, +label, +select, +summary, +textarea, +[tabindex] { + -ms-touch-action: manipulation; +} + +// Pico +// –––––––––––––––––––– + +[dir="rtl"] { + direction: rtl; +} diff --git a/app/assets/stylesheets/utilities/_loading.scss b/app/assets/stylesheets/utilities/_loading.scss new file mode 100644 index 0000000..c6f3afe --- /dev/null +++ b/app/assets/stylesheets/utilities/_loading.scss @@ -0,0 +1,58 @@ +/** + * Loading ([aria-busy=true]) + */ + + +// Cursor +[aria-busy="true"] { + cursor: progress; +} + +// Everyting except form elements +[aria-busy="true"]:not(input, select, textarea, html) { + + &::before { + display: inline-block; + width: 1em; + height: 1em; + border: 0.1875em solid currentColor; + border-radius: 1em; + border-right-color: transparent; + content: ""; + vertical-align: text-bottom; + vertical-align: -.125em; // Visual alignment + animation: spinner 0.75s linear infinite; + opacity: var(--loading-spinner-opacity); + } + + &:not(:empty) { + &::before { + margin-right: calc(var(--spacing) * 0.5); + margin-left: 0; + margin-inline-start: 0; + margin-inline-end: calc(var(--spacing) * 0.5); + } + } + + &:empty { + text-align: center; + } +} + +// Buttons and links +button, +input[type="submit"], +input[type="button"], +input[type="reset"], +a { + &[aria-busy="true"] { + pointer-events: none; + } +} + +// Animation: rotate +@keyframes spinner { + to { + transform: rotate(360deg); + } +} diff --git a/app/assets/stylesheets/utilities/_reduce-motion.scss b/app/assets/stylesheets/utilities/_reduce-motion.scss new file mode 100644 index 0000000..ecfd6fd --- /dev/null +++ b/app/assets/stylesheets/utilities/_reduce-motion.scss @@ -0,0 +1,27 @@ +@if $enable-transitions and $enable-important { + /** + * Reduce Motion Features + */ + + // Based on : + // - sanitize.css v13.0.0 | CC0 1.0 Universal | github.com/csstools/sanitize.css + // –––––––––––––––––––– + + // 1. Remove animations when motion is reduced (opinionated) + // 2. Remove fixed background attachments when motion is reduced (opinionated) + // 3. Remove timed scrolling behaviors when motion is reduced (opinionated) + // 4. Remove transitions when motion is reduced (opinionated) + @media (prefers-reduced-motion: reduce) { + *:not([aria-busy="true"]), + :not([aria-busy="true"])::before, + :not([aria-busy="true"])::after { + background-attachment: initial !important; // 2 + animation-duration: 1ms !important; // 1 + animation-delay: -1ms !important; // 1 + animation-iteration-count: 1 !important; // 1 + scroll-behavior: auto !important; // 3 + transition-delay: 0s !important; // 4 + transition-duration: 0s !important; // 4 + } + } +} diff --git a/app/assets/stylesheets/utilities/_tooltip.scss b/app/assets/stylesheets/utilities/_tooltip.scss new file mode 100644 index 0000000..d0355a3 --- /dev/null +++ b/app/assets/stylesheets/utilities/_tooltip.scss @@ -0,0 +1,278 @@ +/** + * Tooltip ([data-tooltip]) + */ + +[data-tooltip] { + position: relative; + + &:not(a, button, input) { + border-bottom: 1px dotted; + text-decoration: none; + cursor: help; + } + + &[data-placement="top"]::before, + &[data-placement="top"]::after, + &::before, + &::after { + display: block; + z-index: 99; + position: absolute; + bottom: 100%; + left: 50%; + padding: .25rem .5rem; + overflow: hidden; + transform: translate(-50%, -.25rem); + border-radius: var(--border-radius); + background: var(--tooltip-background-color); + content: attr(data-tooltip); + color: var(--tooltip-color); + font-style: normal; + font-weight: var(--font-weight); + font-size: .875rem; + text-decoration: none; + text-overflow: ellipsis; + white-space: nowrap; + opacity: 0; + pointer-events: none; + } + + // Caret + &[data-placement="top"]::after, + &::after { + padding: 0; + transform: translate(-50%, 0rem); + border-top: .3rem solid; + border-right: .3rem solid transparent; + border-left: .3rem solid transparent; + border-radius: 0; + background-color: transparent; + content: ""; + color: var(--tooltip-background-color); + } + + &[data-placement="bottom"] { + &::before, + &::after { + top: 100%; + bottom: auto; + transform: translate(-50%, .25rem); + } + + &:after{ + transform: translate(-50%, -.3rem); + border: .3rem solid transparent; + border-bottom: .3rem solid; + } + } + + &[data-placement="left"] { + &::before, + &::after { + top: 50%; + right: 100%; + bottom: auto; + left: auto; + transform: translate(-.25rem, -50%); + } + + &:after{ + transform: translate(.3rem, -50%); + border: .3rem solid transparent; + border-left: .3rem solid; + } + } + + &[data-placement="right"] { + &::before, + &::after { + top: 50%; + right: auto; + bottom: auto; + left: 100%; + transform: translate(.25rem, -50%); + } + + &:after{ + transform: translate(-.3rem, -50%); + border: .3rem solid transparent; + border-right: .3rem solid; + } + } + + // Display + &:focus, + &:hover { + &::before, + &::after { + opacity: 1; + } + } + + + @if $enable-transitions { + + // Animations, excluding touch devices + @media (hover: hover) and (pointer: fine) { + &[data-placement="bottom"]:focus, + &[data-placement="bottom"]:hover + &:focus, + &:hover { + &::before, + &::after { + animation-duration: .2s; + animation-name: tooltip-slide-top; + } + + &::after { + animation-name: tooltip-caret-slide-top; + } + } + + &[data-placement="bottom"] { + &:focus, + &:hover { + &::before, + &::after { + animation-duration: .2s; + animation-name: tooltip-slide-bottom; + } + + &::after { + animation-name: tooltip-caret-slide-bottom; + } + } + } + + &[data-placement="left"] { + &:focus, + &:hover { + &::before, + &::after { + animation-duration: .2s; + animation-name: tooltip-slide-left; + } + + &::after { + animation-name: tooltip-caret-slide-left; + } + } + } + + &[data-placement="right"] { + &:focus, + &:hover { + &::before, + &::after { + animation-duration: .2s; + animation-name: tooltip-slide-right; + } + + &::after { + animation-name: tooltip-caret-slide-right; + } + } + } + } + + @keyframes tooltip-slide-top { + from { + transform: translate(-50%, .75rem); + opacity: 0; + } + to { + transform: translate(-50%, -.25rem); + opacity: 1; + } + } + + @keyframes tooltip-caret-slide-top { + from { + opacity: 0; + } + 50% { + transform: translate(-50%, -.25rem); + opacity: 0; + } + to { + transform: translate(-50%, 0rem); + opacity: 1; + } + } + + @keyframes tooltip-slide-bottom { + from { + transform: translate(-50%, -.75rem); + opacity: 0; + } + to { + transform: translate(-50%, .25rem); + opacity: 1; + } + } + + @keyframes tooltip-caret-slide-bottom { + from { + opacity: 0; + } + 50% { + transform: translate(-50%, -.5rem); + opacity: 0; + } + to { + transform: translate(-50%, -.3rem); + opacity: 1; + } + } + + @keyframes tooltip-slide-left { + from { + transform: translate(.75rem, -50%); + opacity: 0; + } + to { + transform: translate(-.25rem, -50%); + opacity: 1; + } + } + + @keyframes tooltip-caret-slide-left { + from { + opacity: 0; + } + 50% { + transform: translate(.05rem, -50%); + opacity: 0; + } + to { + transform: translate(.3rem, -50%); + opacity: 1; + } + } + + @keyframes tooltip-slide-right { + from { + transform: translate(-.75rem, -50%); + opacity: 0; + } + to { + transform: translate(.25rem, -50%); + opacity: 1; + } + } + + @keyframes tooltip-caret-slide-right { + from { + opacity: 0; + } + 50% { + transform: translate(-.05rem, -50%); + opacity: 0; + } + to { + transform: translate(-.3rem, -50%); + opacity: 1; + } + } + } +} diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 0000000..d672697 --- /dev/null +++ b/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 0000000..0ff5442 --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 0000000..09705d1 --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/controllers/concerns/.keep diff --git a/app/controllers/leagues_controller.rb b/app/controllers/leagues_controller.rb new file mode 100644 index 0000000..102a9bd --- /dev/null +++ b/app/controllers/leagues_controller.rb @@ -0,0 +1,16 @@ +class LeaguesController < ApplicationController + def index + @leagues = League.all + end + + def show + league_id = params[:id] + @league = League.find_by(league_id: league_id) + + @q = LeaguePickScore.where(league_id: league_id).ransack(params[:q]) + @q.sorts = 'total desc' if @q.sorts.empty? + @scores = @q.result(distinct: true) + + @player_scores = PlayerScore.where(league_id: league_id) + end +end diff --git a/app/controllers/rodauth_controller.rb b/app/controllers/rodauth_controller.rb new file mode 100644 index 0000000..469db79 --- /dev/null +++ b/app/controllers/rodauth_controller.rb @@ -0,0 +1,4 @@ +class RodauthController < ApplicationController + # used by Rodauth for rendering views, CSRF protection, and running any + # registered action callbacks and rescue_from handlers +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000..de6be79 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/app/helpers/leagues_helper.rb b/app/helpers/leagues_helper.rb new file mode 100644 index 0000000..cf00be6 --- /dev/null +++ b/app/helpers/leagues_helper.rb @@ -0,0 +1,2 @@ +module LeaguesHelper +end diff --git a/app/javascript/application.js b/app/javascript/application.js new file mode 100644 index 0000000..0d7b494 --- /dev/null +++ b/app/javascript/application.js @@ -0,0 +1,3 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js new file mode 100644 index 0000000..1213e85 --- /dev/null +++ b/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/app/javascript/controllers/hello_controller.js b/app/javascript/controllers/hello_controller.js new file mode 100644 index 0000000..5975c07 --- /dev/null +++ b/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js new file mode 100644 index 0000000..54ad4ca --- /dev/null +++ b/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 0000000..d394c3d --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb new file mode 100644 index 0000000..3c34c81 --- /dev/null +++ b/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/app/mailers/rodauth_mailer.rb b/app/mailers/rodauth_mailer.rb new file mode 100644 index 0000000..6782c87 --- /dev/null +++ b/app/mailers/rodauth_mailer.rb @@ -0,0 +1,62 @@ +class RodauthMailer < ApplicationMailer + default to: -> { @rodauth.email_to }, from: -> { @rodauth.email_from } + + def verify_account(name, account_id, key) + @rodauth = rodauth(name, account_id) { @verify_account_key_value = key } + @account = @rodauth.rails_account + + mail subject: @rodauth.email_subject_prefix + @rodauth.verify_account_email_subject + end + + def reset_password(name, account_id, key) + @rodauth = rodauth(name, account_id) { @reset_password_key_value = key } + @account = @rodauth.rails_account + + mail subject: @rodauth.email_subject_prefix + @rodauth.reset_password_email_subject + end + + def verify_login_change(name, account_id, key) + @rodauth = rodauth(name, account_id) { @verify_login_change_key_value = key } + @account = @rodauth.rails_account + @new_email = @account.login_change_key.login + + mail to: @new_email, subject: @rodauth.email_subject_prefix + @rodauth.verify_login_change_email_subject + end + + def password_changed(name, account_id) + @rodauth = rodauth(name, account_id) + @account = @rodauth.rails_account + + mail subject: @rodauth.email_subject_prefix + @rodauth.password_changed_email_subject + end + + # def reset_password_notify(name, account_id) + # @rodauth = rodauth(name, account_id) + # @account = @rodauth.rails_account + + # mail subject: @rodauth.email_subject_prefix + @rodauth.reset_password_notify_email_subject + # end + + # def email_auth(name, account_id, key) + # @rodauth = rodauth(name, account_id) { @email_auth_key_value = key } + # @account = @rodauth.rails_account + + # mail subject: @rodauth.email_subject_prefix + @rodauth.email_auth_email_subject + # end + + # def unlock_account(name, account_id, key) + # @rodauth = rodauth(name, account_id) { @unlock_account_key_value = key } + # @account = @rodauth.rails_account + + # mail subject: @rodauth.email_subject_prefix + @rodauth.unlock_account_email_subject + # end + + private + + def rodauth(name, account_id, &block) + instance = RodauthApp.rodauth(name).allocate + instance.instance_eval { @account = account_ds(account_id).first! } + instance.instance_eval(&block) if block + instance + end +end diff --git a/app/misc/rodauth_app.rb b/app/misc/rodauth_app.rb new file mode 100644 index 0000000..6372422 --- /dev/null +++ b/app/misc/rodauth_app.rb @@ -0,0 +1,25 @@ +class RodauthApp < Rodauth::Rails::App + # primary configuration + configure RodauthMain + + # secondary configuration + # configure RodauthAdmin, :admin + + route do |r| + rodauth.load_memory # autologin remembered users + + r.rodauth # route rodauth requests + + # ==> Authenticating requests + # Call `rodauth.require_account` for requests that you want to + # require authentication for. For example: + # + # # authenticate /dashboard/* and /account/* requests + # if r.path.start_with?("/dashboard") || r.path.start_with?("/account") + # rodauth.require_account + # end + + # ==> Secondary configurations + # r.rodauth(:admin) # route admin rodauth requests + end +end diff --git a/app/misc/rodauth_main.rb b/app/misc/rodauth_main.rb new file mode 100644 index 0000000..fb19c9a --- /dev/null +++ b/app/misc/rodauth_main.rb @@ -0,0 +1,182 @@ +require "sequel/core" + +class RodauthMain < Rodauth::Rails::Auth + configure do + # List of authentication features that are loaded. + enable :create_account, :verify_account, :verify_account_grace_period, + :login, :logout, :remember, + :reset_password, :change_password, :change_password_notify, + :change_login, :verify_login_change, :close_account, :argon2 + + # See the Rodauth documentation for the list of available config options: + # http://rodauth.jeremyevans.net/documentation.html + + # ==> General + # Initialize Sequel and have it reuse Active Record's database connection. + db Sequel.postgres(extensions: :activerecord_connection, keep_reference: false) + + # Avoid DB query that checks accounts table schema at boot time. + convert_token_id_to_integer? true + + # Change prefix of table and foreign key column names from default "account" + # accounts_table :users + # verify_account_table :user_verification_keys + # verify_login_change_table :user_login_change_keys + # reset_password_table :user_password_reset_keys + # remember_table :user_remember_keys + + # The secret key used for hashing public-facing tokens for various features. + # Defaults to Rails `secret_key_base`, but you can use your own secret key. + # hmac_secret "43a2cc4e1fbca7754f14061ff7bbcdb56db757e84a89af87e9fe388f0d39aa6faf4ff347f07c50b95e4cff27a4fe15111960aeaf859ba559e4cb6bba94e38ad9" + + # Use a rotatable password pepper when hashing passwords with Argon2. + # argon2_secret { hmac_secret } + + # Since we're using argon2, prevent loading the bcrypt gem to save memory. + require_bcrypt? false + + # Use path prefix for all routes. + # prefix "/auth" + + # Specify the controller used for view rendering, CSRF, and callbacks. + rails_controller { RodauthController } + + # Make built-in page titles accessible in your views via an instance variable. + title_instance_variable :@page_title + + # Store account status in an integer column without foreign key constraint. + account_status_column :status + + # Store password hash in a column instead of a separate table. + account_password_hash_column :password_hash + + # Set password when creating account instead of when verifying. + verify_account_set_password? false + + # Change some default param keys. + login_param "email" + login_confirm_param "email-confirm" + # password_confirm_param "confirm_password" + + # Redirect back to originally requested location after authentication. + # login_return_to_requested_location? true + # two_factor_auth_return_to_requested_location? true # if using MFA + + # Autologin the user after they have reset their password. + # reset_password_autologin? true + + # Delete the account record when the user has closed their account. + # delete_account_on_close? true + + # Redirect to the app from login and registration pages if already logged in. + # already_logged_in { redirect login_redirect } + + # ==> Emails + # Use a custom mailer for delivering authentication emails. + create_reset_password_email do + RodauthMailer.reset_password(self.class.configuration_name, account_id, reset_password_key_value) + end + create_verify_account_email do + RodauthMailer.verify_account(self.class.configuration_name, account_id, verify_account_key_value) + end + create_verify_login_change_email do |_login| + RodauthMailer.verify_login_change(self.class.configuration_name, account_id, verify_login_change_key_value) + end + create_password_changed_email do + RodauthMailer.password_changed(self.class.configuration_name, account_id) + end + # create_reset_password_notify_email do + # RodauthMailer.reset_password_notify(self.class.configuration_name, account_id) + # end + # create_email_auth_email do + # RodauthMailer.email_auth(self.class.configuration_name, account_id, email_auth_key_value) + # end + # create_unlock_account_email do + # RodauthMailer.unlock_account(self.class.configuration_name, account_id, unlock_account_key_value) + # end + send_email do |email| + # queue email delivery on the mailer after the transaction commits + db.after_commit { email.deliver_later } + end + + # ==> Flash + # Match flash keys with ones already used in the Rails app. + # flash_notice_key :success # default is :notice + # flash_error_key :error # default is :alert + + # Override default flash messages. + # create_account_notice_flash "Your account has been created. Please verify your account by visiting the confirmation link sent to your email address." + # require_login_error_flash "Login is required for accessing this page" + # login_notice_flash nil + + # ==> Validation + # Override default validation error messages. + # no_matching_login_message "user with this email address doesn't exist" + # already_an_account_with_this_login_message "user with this email address already exists" + # password_too_short_message { "needs to have at least #{password_minimum_length} characters" } + # login_does_not_meet_requirements_message { "invalid email#{", #{login_requirement_message}" if login_requirement_message}" } + + # Passwords shorter than 8 characters are considered weak according to OWASP. + password_minimum_length 8 + # Having a maximum password length set prevents long password DoS attacks. + password_maximum_length 64 + + # Custom password complexity requirements (alternative to password_complexity feature). + # password_meets_requirements? do |password| + # super(password) && password_complex_enough?(password) + # end + # auth_class_eval do + # def password_complex_enough?(password) + # return true if password.match?(/\d/) && password.match?(/[^a-zA-Z\d]/) + # set_password_requirement_error_message(:password_simple, "requires one number and one special character") + # false + # end + # end + + # ==> Remember Feature + # Remember all logged in users. + after_login { remember_login } + + # Or only remember users that have ticked a "Remember Me" checkbox on login. + # after_login { remember_login if param_or_nil("remember") } + + # Extend user's remember period when remembered via a cookie + extend_remember_deadline? true + + # ==> Hooks + # Validate custom fields in the create account form. + # before_create_account do + # throw_error_status(422, "name", "must be present") if param("name").empty? + # end + + # Perform additional actions after the account is created. + # after_create_account do + # Profile.create!(account_id: account_id, name: param("name")) + # end + + # Do additional cleanup after the account is closed. + # after_close_account do + # Profile.find_by!(account_id: account_id).destroy + # end + + # ==> Redirects + # Redirect to home page after logout. + logout_redirect "/" + + # Redirect to wherever login redirects to after account verification. + verify_account_redirect { login_redirect } + + # Redirect to login page after password reset. + reset_password_redirect { login_path } + + # Ensure requiring login follows login route changes. + require_login_redirect { login_path } + + # ==> Deadlines + # Change default deadlines for some actions. + # verify_account_grace_period 3.days.to_i + # reset_password_deadline_interval Hash[hours: 6] + # verify_login_change_deadline_interval Hash[days: 2] + # remember_deadline_interval Hash[days: 30] + end +end diff --git a/app/models/account.rb b/app/models/account.rb new file mode 100644 index 0000000..4fcee6f --- /dev/null +++ b/app/models/account.rb @@ -0,0 +1,4 @@ +class Account < ApplicationRecord + include Rodauth::Model(RodauthMain) + enum :status, unverified: 1, verified: 2, closed: 3 +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb new file mode 100644 index 0000000..b63caeb --- /dev/null +++ b/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/models/concerns/.keep diff --git a/app/models/league.rb b/app/models/league.rb new file mode 100644 index 0000000..ff967fc --- /dev/null +++ b/app/models/league.rb @@ -0,0 +1,2 @@ +class League < ApplicationRecord +end diff --git a/app/models/league_pick_score.rb b/app/models/league_pick_score.rb new file mode 100644 index 0000000..f7c6521 --- /dev/null +++ b/app/models/league_pick_score.rb @@ -0,0 +1,7 @@ +class LeaguePickScore < ApplicationRecord + def self.ransackable_attributes(auth_object = nil) + ["champion", "conference", "divisional", "league_id", "player", "playoffs", "superbowl", "team", "total", "win"] + end + + # private_class_method :ransackable_attributes +end diff --git a/app/models/player_score.rb b/app/models/player_score.rb new file mode 100644 index 0000000..a79e4a7 --- /dev/null +++ b/app/models/player_score.rb @@ -0,0 +1,2 @@ +class PlayerScore < ApplicationRecord +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000..2e2c0cc --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> + <head> + <title>Team Draft</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + </head> + + <body> + <nav class="container-fluid"> + <ul> + <li> + <a href="/" class="contrast"><strong>Team Draft</strong></a> + </li> + </ul> + </nav> + <main class="container"> + <%= yield %> + </main> + </body> +</html> diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 0000000..3aac900 --- /dev/null +++ b/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <style> + /* Email styles need to be inline */ + </style> + </head> + + <body> + <%= yield %> + </body> +</html> diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb new file mode 100644 index 0000000..37f0bdd --- /dev/null +++ b/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/app/views/leagues/index.html.erb b/app/views/leagues/index.html.erb new file mode 100644 index 0000000..bcfa1fd --- /dev/null +++ b/app/views/leagues/index.html.erb @@ -0,0 +1,3 @@ +<% @leagues.each do |league| %> + <p><%= link_to league.name, league_path(league) %></p> +<% end %> diff --git a/app/views/leagues/show.html.erb b/app/views/leagues/show.html.erb new file mode 100644 index 0000000..d7a43d3 --- /dev/null +++ b/app/views/leagues/show.html.erb @@ -0,0 +1,33 @@ +<div class="grid"> +<% @player_scores.each do |score| %> + <div><strong><%= score.player %> - <%= score.score %></strong></div> +<% end %> +</div> +<table role="grid"> + <thead> + <tr> + <th scope="col"><%= sort_link(@q, :team) %></th> + <th scope="col"><%= sort_link(@q, :player) %></th> + <th scope="col" style="width: 11em"><%= sort_link(@q, :total) %></th> + </tr> + </thead> + <tbody> + <% @scores.each do |score| %> + <tr> + <td scope="row"><%= score.team %></td> + <td><%= score.player %></td> + <td> + <%= score.total %> + <p> + <small><strong><abbr title="Wins">W</abbr>:</strong> <%= score.win %></small> + <% if score.playoffs > 0 %><small><strong><abbr title="Made the Playoffs">P</abbr>:</strong> <%= score.playoffs %></small><% end %> + <% if score.divisional > 0 %><small><strong><abbr title="Made the Divisional Round">D</abbr>:</strong> <%= score.divisional %></small><% end %> + <% if score.conference > 0 %><small><strong><abbr title="Made the Conference Championship">C</abbr>:</strong> <%= score.conference %></small><% end %> + <% if score.superbowl > 0 %><small><strong><abbr title="Made the Superbowl">S</abbr>:</strong> <%= score.superbowl %></small><% end %> + <% if score.champion > 0 %><small><strong><abbr title="Superbowl Winner">SW</abbr>:</strong> <%= score.champion %></small><% end %> + </p> + </td> + </tr> + <% end %> + </tbody> +</table> diff --git a/app/views/rodauth_mailer/email_auth.text.erb b/app/views/rodauth_mailer/email_auth.text.erb new file mode 100644 index 0000000..9b989fe --- /dev/null +++ b/app/views/rodauth_mailer/email_auth.text.erb @@ -0,0 +1,5 @@ +Someone has requested a login link for the account with this email +address. If you did not request a login link, please ignore this +message. If you requested a login link, please go to +<%= @rodauth.email_auth_email_link %> +to login to this account. diff --git a/app/views/rodauth_mailer/password_changed.text.erb b/app/views/rodauth_mailer/password_changed.text.erb new file mode 100644 index 0000000..2e48708 --- /dev/null +++ b/app/views/rodauth_mailer/password_changed.text.erb @@ -0,0 +1,2 @@ +Someone (hopefully you) has changed the password for the account +associated to this email address. diff --git a/app/views/rodauth_mailer/reset_password.text.erb b/app/views/rodauth_mailer/reset_password.text.erb new file mode 100644 index 0000000..fcfb698 --- /dev/null +++ b/app/views/rodauth_mailer/reset_password.text.erb @@ -0,0 +1,5 @@ +Someone has requested a password reset for the account with this email +address. If you did not request a password reset, please ignore this +message. If you requested a password reset, please go to +<%= @rodauth.reset_password_email_link %> +to reset the password for the account. diff --git a/app/views/rodauth_mailer/unlock_account.text.erb b/app/views/rodauth_mailer/unlock_account.text.erb new file mode 100644 index 0000000..3d24759 --- /dev/null +++ b/app/views/rodauth_mailer/unlock_account.text.erb @@ -0,0 +1,5 @@ +Someone has requested that the account with this email be unlocked. +If you did not request the unlocking of this account, please ignore this +message. If you requested the unlocking of this account, please go to +<%= @rodauth.unlock_account_email_link %> +to unlock this account. diff --git a/app/views/rodauth_mailer/verify_account.text.erb b/app/views/rodauth_mailer/verify_account.text.erb new file mode 100644 index 0000000..78ff6ad --- /dev/null +++ b/app/views/rodauth_mailer/verify_account.text.erb @@ -0,0 +1,4 @@ +Someone has created an account with this email address. If you did not create +this account, please ignore this message. If you created this account, please go to +<%= @rodauth.verify_account_email_link %> +to verify the account. diff --git a/app/views/rodauth_mailer/verify_login_change.text.erb b/app/views/rodauth_mailer/verify_login_change.text.erb new file mode 100644 index 0000000..693680b --- /dev/null +++ b/app/views/rodauth_mailer/verify_login_change.text.erb @@ -0,0 +1,10 @@ +Someone with an account has requested their login be changed to this email address: + +Old email: <%= @account.email %> + +New email: <%= @new_email %> + +If you did not request this login change, please ignore this message. If you +requested this login change, please go to +<%= @rodauth.verify_login_change_email_link %> +to verify the login change. |