diff --git a/src-react/App.scss b/src-react/App.scss new file mode 100644 index 000000000..c0b93891e --- /dev/null +++ b/src-react/App.scss @@ -0,0 +1,24 @@ +@import "./shared/scss/media"; +@import "./shared/scss/theme_variables"; + +.body-cover { + width: 100%; + z-index: 0; + position: fixed; + height: 100%; +} + +.wrapper { + position: relative; + width: 85%; + min-height: 80px; + margin: 0 auto; + font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; + font-size: 15px; + height: 100%; + line-height: 1.3; + + @media #{$mobile-only} { + width: 100%; + } +} diff --git a/src-react/App.tsx b/src-react/App.tsx index 63d246b24..11adefa16 100644 --- a/src-react/App.tsx +++ b/src-react/App.tsx @@ -1,5 +1,21 @@ +import { useSettings } from './shared/settings/SettingsContext'; +import Footer from './core/footer/Footer'; +import Header from './core/header/Header'; +import './App.scss'; + function App() { - return

Angular 2 HN — React migration in progress

; + const { settings } = useSettings(); + + return ( +
+
+
+
+
+
+
+
+ ); } export default App; diff --git a/src-react/core/footer/Footer.scss b/src-react/core/footer/Footer.scss new file mode 100644 index 000000000..8adac775d --- /dev/null +++ b/src-react/core/footer/Footer.scss @@ -0,0 +1,23 @@ +@import "../../shared/scss/media"; +@import "../../shared/scss/theme_variables"; + +#footer { + position: relative; + padding: 10px; + height: 60px; + letter-spacing: 0.7px; + text-align: center; + + a { + font-weight: bold; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + + @media #{$mobile-only} { + display: none; + } +} diff --git a/src-react/core/footer/Footer.tsx b/src-react/core/footer/Footer.tsx new file mode 100644 index 000000000..188cdb643 --- /dev/null +++ b/src-react/core/footer/Footer.tsx @@ -0,0 +1,16 @@ +import './Footer.scss'; + +function Footer() { + return ( + + ); +} + +export default Footer; diff --git a/src-react/core/header/Header.scss b/src-react/core/header/Header.scss new file mode 100644 index 000000000..75a262e76 --- /dev/null +++ b/src-react/core/header/Header.scss @@ -0,0 +1,149 @@ +@import "../../shared/scss/media"; +@import "../../shared/scss/theme_variables"; + +#header { + color: #fff; + padding: 6px 0; + line-height: 18px; + vertical-align: middle; + position: relative; + z-index: 1; + width: 100%; + + @media #{$mobile-only} { + height: 50px; + position: fixed; + top: 0; + } + + a { + display: inline; + } +} + +.home-link { + width: 50px; + height: 66px; +} + +.logo-inner { + width: 32px; + position: absolute; + left: 17px; + top: 18px; + z-index: -1; + height: 32px; + border-radius: 50%; + + @media #{$mobile-only} { + left: 16px; + top: 12px; + } +} + +.logo { + width: 50px; + padding: 3px 8px 0; + + @media #{$mobile-only} { + width: 45px; + padding: 0 0 0 10px; + } +} + +h1 { + font-weight: normal; + display: inline-block; + vertical-align:middle; + margin: 0; + font-size: 16px; + + a { + color: #fff; + text-decoration: none; + } +} + +.name { + margin-right: 30px; + margin-bottom: 2px; + + @media #{$mobile-only} { + display: none; + } +} + +.header-text { + position: absolute; + width: inherit; + height: 20px; + left: 10px; + top: 27px; + z-index: -1; + + @media #{$mobile-only} { + top: 22px; + } +} + +.left { + position: absolute; + left: 60px; + font-size: 16px; + + @media #{$mobile-only} { + width: 100%; + left: 0; + } +} + +.header-nav { + display: inline-block; + margin-left: 20px; + + @media #{$mobile-only} { + margin-left: 60px; + } + + a { + color: hsla(0,0%,100%,.9); + text-decoration: none; + margin: 0 5px; + letter-spacing: 1.8px; + + &:hover { + color: #fff; + } + } + + .active { + color: #fff; + } +} + +.info { + position: absolute; + top: 0; + right: 20px; + height: 100%; + + @media #{$mobile-only} { + right: 10px; + } + + img { + opacity: 0.8; + width: 25px; + margin-top: 21.5px; + display: block; + + &:hover { + opacity: 1; + cursor: pointer; + } + + @media #{$mobile-only} { + margin-top: 15px; + } + } +} diff --git a/src-react/core/header/Header.tsx b/src-react/core/header/Header.tsx new file mode 100644 index 000000000..3216fce19 --- /dev/null +++ b/src-react/core/header/Header.tsx @@ -0,0 +1,53 @@ +import { NavLink } from 'react-router-dom'; + +import { useSettings } from '../../shared/settings/SettingsContext'; +import Settings from '../settings/Settings'; +import './Header.scss'; + +function Header() { + const { settings, toggleSettings } = useSettings(); + const scrollTop = () => window.scrollTo(0, 0); + + return ( +
+
+ ); +} + +export default Header; diff --git a/src-react/core/settings/Settings.scss b/src-react/core/settings/Settings.scss new file mode 100644 index 000000000..689da6584 --- /dev/null +++ b/src-react/core/settings/Settings.scss @@ -0,0 +1,74 @@ +@import "../../shared/scss/media"; +@import "../../shared/scss/theme_variables"; + +.overlay { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: rgba(0, 0, 0, 0.7); + opacity: 1; + z-index: 1; +} + +.popup { + margin: 70px auto; + padding: 30px; + border-radius: 5px; + width: 30%; + position: relative; + h1 { + margin-top: 0; + margin-bottom: 0px; + color: #fff; + text-align: center; + letter-spacing: 1px; + } + h2 { + padding-top: 10px; + } + hr { + width: 40%; + margin-bottom: 20px; + } + .close { + position: absolute; + top: 12px; + right: 20px; + font-size: 30px; + font-weight: bold; + text-decoration: none; + color: rgba(255,255,255,0.8); + &:hover { + color: #fff; + cursor: pointer; + } + } + .content { + max-height: 30%; + color: #fff; + letter-spacing: 1px; + overflow: auto; + } + input[type=number] { + display: block; + width: 80%; + height: 20px; + margin-bottom: 15px; + border-radius: 5px; + padding: 2px; + } +} + +.control-section { + margin-bottom: 15px; + padding-bottom: 15px; + border-bottom: 1px solid white; +} + +@media screen and (max-width: 700px) { + .box, .popup { + width: 70%; + } +} diff --git a/src-react/core/settings/Settings.tsx b/src-react/core/settings/Settings.tsx new file mode 100644 index 000000000..48bad2982 --- /dev/null +++ b/src-react/core/settings/Settings.tsx @@ -0,0 +1,117 @@ +import type { ChangeEvent, KeyboardEvent } from 'react'; + +import { useSettings } from '../../shared/settings/SettingsContext'; +import './Settings.scss'; + +function Settings() { + const { + settings, + toggleSettings, + toggleOpenLinksInNewTab, + setTheme, + setFont, + setSpacing, + } = useSettings(); + + const changeTitleFont = (event: KeyboardEvent | ChangeEvent) => { + setFont(event.currentTarget.value); + }; + const changeSpacing = (event: KeyboardEvent | ChangeEvent) => { + setSpacing(event.currentTarget.value); + }; + + return ( +
+
+

Settings

+
+ + × + +
+
+

Links

+ + {' Open links in a new tab'} +
+
+
+

Select a theme

+
+ +
+
+ +
+
+ +
+
+
+

Change Font

+
+ +
+
+ +
+
+
+
+
+
+ ); +} + +export default Settings; diff --git a/src-react/main.tsx b/src-react/main.tsx index f3092f2e6..f0e26eb5a 100644 --- a/src-react/main.tsx +++ b/src-react/main.tsx @@ -1,10 +1,17 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; import App from './App'; +import { SettingsProvider } from './shared/settings/SettingsContext'; +import './styles.scss'; createRoot(document.getElementById('root')!).render( - + + + + + , ); diff --git a/src-react/shared/components/error-message/ErrorMessage.scss b/src-react/shared/components/error-message/ErrorMessage.scss new file mode 100644 index 000000000..eb5cc01e8 --- /dev/null +++ b/src-react/shared/components/error-message/ErrorMessage.scss @@ -0,0 +1,115 @@ +@import "../../scss/media"; +@import "../../scss/theme_variables"; + +.error-section { + height: 300px; + margin: 200px; + + @media #{$mobile-only} { + height: 0; + display: block; + position: relative; + margin: 30vh 0; + } + + p { + text-align: center; + padding: 0 25px; + + &.strong { + margin-top: 25px; + font-weight: bold; + } + } + + .skull { + width: $skull-size; + height: $skull-size; + position: relative; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + + .head { + width: 100%; + height: 75%; + border-radius: 15% / 20%; + position: absolute; + top: 0; + left: 0; + &:before, &:after { + content: ""; + position: absolute; + border-radius: 50%; + width: 20%; + height: 30%; + bottom: 10%; + } + &:before { + left: 10%; + } + &:after { + right: 10%; + } + .crack { + width: 10%; + height: 10%; + position: absolute; + top: 0; + right: 25%; + transform: skew(-15deg); + + &:before { + content: ""; + position: absolute; + top: 100%; + left: $skull-size / 15; + border-right: $skull-size / 20 solid transparent; + border-left: $skull-size / 40 solid transparent; + } + } + } + .mouth { + width: 40%; + height: 25%; + position: absolute; + top: 75%; + left: 30%; + border-radius: 0 0 $skull-size / 10 $skull-size / 10; + &:before { + content: ""; + position: absolute; + width: 15%; + height: 50%; + border-radius: 50% / 30%; + left: 42.5%; + top: -25%; + } + .teeth { + position: absolute; + bottom: 0; + left: 45%; + width: 10%; + height: 50%; + margin-bottom: -5%; + border-radius: 50% / 20%; + + &:before, &:after { + content: ""; + position: absolute; + width: 100%; + height: 100%; + border-radius: 50% / 20%; + } + &:before { + left: -250%; + } + &:after { + right: -250%; + } + } + } + } +} diff --git a/src-react/shared/components/error-message/ErrorMessage.tsx b/src-react/shared/components/error-message/ErrorMessage.tsx new file mode 100644 index 000000000..ac889ba5b --- /dev/null +++ b/src-react/shared/components/error-message/ErrorMessage.tsx @@ -0,0 +1,24 @@ +import './ErrorMessage.scss'; + +interface ErrorMessageProps { + message: string; +} + +function ErrorMessage({ message }: ErrorMessageProps) { + return ( +
+
+
+
+
+
+
+
+
+

{message}

+

If you are offline viewing, you'll need to visit this page with a network connection first before it can work offline.

+
+ ); +} + +export default ErrorMessage; diff --git a/src-react/shared/components/loader/Loader.scss b/src-react/shared/components/loader/Loader.scss new file mode 100644 index 000000000..625d0fe8b --- /dev/null +++ b/src-react/shared/components/loader/Loader.scss @@ -0,0 +1,109 @@ +@import "../../scss/media"; +@import "../../scss/theme_variables"; + +.loader { + -webkit-animation: load1 1s infinite ease-in-out; + animation: load1 1s infinite ease-in-out; + width: 1em; + height: 4em; + &:before, &:after { + -webkit-animation: load1 1s infinite ease-in-out; + animation: load1 1s infinite ease-in-out; + width: 1em; + height: 4em; + } + &:before, &:after { + position: absolute; + top: 0; + content: ''; + } + &:before { + left: -1.5em; + -webkit-animation-delay: -0.32s; + animation-delay: -0.32s; + } +} + +.loading-section { + height: 70px; + margin: 40px 0 40px 40px; + + @media #{$mobile-only} { + display: block; + position: relative; + margin: 45vh 0; + } +} + +.loader { + text-indent: -9999em; + margin: 20px 20px; + position: relative; + font-size: 11px; + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); + -webkit-animation-delay: -0.16s; + animation-delay: -0.16s; + &:after { + left: 1.5em; + } + + @media #{$mobile-only} { + margin: 20px auto; + } +} + +@-webkit-keyframes load1 { + 0%, + 80%, + 100% { + box-shadow: 0 0; + height: 2em; + } + 40% { + box-shadow: 0 -2em; + height: 3em; + } +} + +@keyframes load1 { + 0%, + 80%, + 100% { + box-shadow: 0 0; + height: 2em; + } + 40% { + box-shadow: 0 -2em; + height: 3em; + } +} + +@media #{$mobile-only} { + @-webkit-keyframes load1 { + 0%, + 80%, + 100% { + box-shadow: 0 0; + height: 4em; + } + 40% { + box-shadow: 0 -2em; + height: 5em; + } + } + + @keyframes load1 { + 0%, + 80%, + 100% { + box-shadow: 0 0; + height: 3em; + } + 40% { + box-shadow: 0 -2em; + height: 4em; + } + } +} diff --git a/src-react/shared/components/loader/Loader.tsx b/src-react/shared/components/loader/Loader.tsx new file mode 100644 index 000000000..c1f549ab8 --- /dev/null +++ b/src-react/shared/components/loader/Loader.tsx @@ -0,0 +1,11 @@ +import './Loader.scss'; + +function Loader() { + return ( +
+
Loading...
+
+ ); +} + +export default Loader; diff --git a/src-react/shared/scss/_media.scss b/src-react/shared/scss/_media.scss new file mode 100644 index 000000000..b04b71a92 --- /dev/null +++ b/src-react/shared/scss/_media.scss @@ -0,0 +1,3 @@ +$mobile-only: "only screen and (max-width : 768px)"; +$laptop-only: "only screen and (min-width : 769px)"; +$tablet-only: "only screen and (max-width : 1024px)"; diff --git a/src-react/shared/scss/_theme_variables.scss b/src-react/shared/scss/_theme_variables.scss new file mode 100644 index 000000000..41dae5eea --- /dev/null +++ b/src-react/shared/scss/_theme_variables.scss @@ -0,0 +1,37 @@ +$skull-size: 200px; + +// ------------------------------------------------------------------------------------------- +// Theme Engine +// ------------------------------------------------------------------------------------------- + +// Day theme colors +$theme-day-body-background-color: #fff; +$theme-day-wrapper-background-color: #f5f5f5; +$theme-day-wrapper-mobile-background-color: #fff; +$theme-day-text-color: #000; +$theme-day-subtext-color: #696969; +$theme-day-secondary-color: #b92b27; +$theme-day-header-background-color: $theme-day-secondary-color; +$theme-day-logo-inner: #fff; + +// Day theme extras +$theme-day-border: 2px solid #b92b27; + +// Night theme colors +$theme-night-body-background-color: #37474F; +$theme-night-wrapper-background-color: #263238; +$theme-night-wrapper-mobile-background-color: $theme-night-wrapper-background-color; +$theme-night-text-color: rgba(255, 255, 255, 0.7); +$theme-night-subtext-color: #999; +$theme-night-secondary-color: #00c0ff; +$theme-night-header-background-color: #263238; +$theme-night-logo-inner: $theme-night-header-background-color; + +// Night theme extras +$theme-night-border: 2px solid #00c0ff; + +// Black theme colors +$theme-amoledblack-body-background-color: #000; +$theme-amoledblack-text-color: rgba(255, 255, 255, 0.75); +$theme-amoledblack-subtext-color: rgba(255, 255, 255, 0.5); +$theme-amoledblack-secondary-color: rgba(255, 255, 255, 0.6); diff --git a/src-react/shared/scss/_themes.scss b/src-react/shared/scss/_themes.scss new file mode 100644 index 000000000..231ed7cf9 --- /dev/null +++ b/src-react/shared/scss/_themes.scss @@ -0,0 +1,245 @@ +@import "./media"; +@import "./theme_variables"; + +/* ---------------------------------- + + Want a new theme? Add your new theme variables here! + +---------------------------------- */ + +@mixin theme( + $name, + $body-background-color, + $wrapper-background-color, + $wrapper-mobile-background-color, + $wrapper-color, + $item-a-color, + $item-a-visited-color, + $header-background-color, + $subtext-color, + $secondary-link-color, + $logo-inner-color, + $border +) { + .#{$name} { + .body-cover { + background: $body-background-color; + + @media #{$mobile-only} { + background: $wrapper-mobile-background-color; + } + } + + .wrapper { + background: $wrapper-background-color; + color: $wrapper-color; + + @media #{$mobile-only} { + background: $wrapper-mobile-background-color; + } + + a { + color: $item-a-color; + + &:visited { + color: $item-a-visited-color; + } + } + + #header { + background: $header-background-color; + border-bottom: $border; + } + + .logo-inner { + background: $logo-inner-color; + } + + .nav { + a { + color: $secondary-link-color; + + @media #{$mobile-only} { + color: $secondary-link-color; + } + } + } + + #footer { + border-top: $border; + + a { + color: $secondary-link-color; + } + } + + .subtext, .subtext-palm, .subtext-laptop, .domain, .meta, .deleted-meta{ + color: $subtext-color; + + a { + color: $secondary-link-color; + } + } + + .popup { + background: $header-background-color; + } + + .item-header { + border-bottom: $border; + + @media #{$mobile-only} { + background: $wrapper-mobile-background-color; + } + } + + .pollContent { + .pollBar { + background: $secondary-link-color; + } + } + + .loader { + color: $secondary-link-color; + background: $secondary-link-color; + + &:before, &:after { + background: $secondary-link-color; + } + } + + .job-header { + @media #{$mobile-only} { + border-bottom: $border; + } + } + + .back-button { + @media #{$mobile-only} { + border-top: .3rem solid $secondary-link-color; + border-right: .3rem solid $secondary-link-color; + } + } + + .error-section { + .skull { + .head { + background-color: $secondary-link-color; + + &:before, &:after { + background-color: $wrapper-background-color; + + @media #{$mobile-only} { + background-color: $wrapper-mobile-background-color; + } + } + + .crack { + background-color: $wrapper-background-color; + + @media #{$mobile-only} { + background-color: $wrapper-mobile-background-color; + } + + &:before { + border-top: $skull-size / 8 solid $wrapper-background-color; + + @media #{$mobile-only} { + border-top: $skull-size / 8 solid $wrapper-mobile-background-color; + } + } + } + } + + .mouth { + background-color: $secondary-link-color; + + &:before { + background-color: $wrapper-background-color; + + @media #{$mobile-only} { + background-color: $wrapper-mobile-background-color; + } + } + } + + .teeth { + background-color: $wrapper-background-color; + + @media #{$mobile-only} { + background-color: $wrapper-mobile-background-color; + } + + &:before, &:after { + background-color: $wrapper-background-color; + + @media #{$mobile-only} { + background-color: $wrapper-mobile-background-color; + } + } + } + } + } + + .main-details { + .name { + color: $secondary-link-color; + } + .right { + color: $secondary-link-color; + } + } + } + } +} + +@include theme( + default, + $theme-day-body-background-color, + $theme-day-wrapper-background-color, + $theme-day-wrapper-mobile-background-color, + $theme-day-text-color, + $theme-day-text-color, + $theme-day-subtext-color, + $theme-day-header-background-color, + $theme-day-subtext-color, + $theme-day-secondary-color, + $theme-day-logo-inner, + $theme-day-border +); + +@include theme( + night, + $theme-night-body-background-color, + $theme-night-wrapper-background-color, + $theme-night-wrapper-mobile-background-color, + $theme-night-text-color, + $theme-night-text-color, + $theme-night-subtext-color, + $theme-night-header-background-color, + $theme-night-subtext-color, + $theme-night-secondary-color, + $theme-night-logo-inner, + $theme-night-border +); + +@include theme( + amoledblack, + $theme-amoledblack-body-background-color, + $theme-amoledblack-body-background-color, + $theme-amoledblack-body-background-color, + $theme-amoledblack-text-color, + $theme-amoledblack-text-color, + darken($theme-amoledblack-text-color, 33%), + $theme-amoledblack-body-background-color, + $theme-amoledblack-subtext-color, + $theme-amoledblack-secondary-color, + $theme-amoledblack-body-background-color, + $theme-amoledblack-secondary-color +); + +/* ---------------------------------- + + Include your new theme here as well as the settings component + +---------------------------------- */ diff --git a/src-react/styles.scss b/src-react/styles.scss new file mode 100644 index 000000000..d5650d1d4 --- /dev/null +++ b/src-react/styles.scss @@ -0,0 +1,16 @@ +@import "./shared/scss/themes"; + +html { + height: 100%; + width: 100%; +} + +body { + margin: 0; + height: 100%; + width: 100%; +} + +pre { + white-space: pre-wrap; +}