diff --git a/client/config/webpack.config.js b/client/config/webpack.config.js index 96d83038f9d9..61670a3174a2 100644 --- a/client/config/webpack.config.js +++ b/client/config/webpack.config.js @@ -40,12 +40,13 @@ function config(webpackEnv) { const env = getClientEnvironment(); // common function to get style loaders - const getStyleLoaders = (cssOptions, preProcessor) => { + const getStyleLoaders = (cssOptions, preProcessor, extract = true) => { const loaders = [ - isEnvDevelopment && resolve.sync("style-loader"), - isEnvProduction && { - loader: MiniCssExtractPlugin.loader, - }, + extract && isEnvDevelopment && resolve.sync("style-loader"), + extract && + isEnvProduction && { + loader: MiniCssExtractPlugin.loader, + }, { loader: resolve.sync("css-loader"), options: cssOptions, @@ -243,10 +244,6 @@ function config(webpackEnv) { resourceQuery: /raw/, type: "asset/source", }, - { - resourceQuery: /url/, - type: "asset/resource", - }, { test: [/\.avif$/, /\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], type: "asset/resource", @@ -362,6 +359,22 @@ function config(webpackEnv) { sideEffects: true, }, // Opt-in support for SASS (using .scss or .sass extensions). + { + test: /\.(scss|sass)$/, + with: { type: "css" }, + use: getStyleLoaders( + { + importLoaders: 3, + sourceMap: isEnvProduction + ? shouldUseSourceMap + : isEnvDevelopment, + exportType: "css-style-sheet", + }, + "sass-loader", + false + ), + sideEffects: true, + }, { test: /\.(scss|sass)$/, use: getStyleLoaders( diff --git a/client/package.json b/client/package.json index f1ad9b7ab58a..58acd2b44593 100644 --- a/client/package.json +++ b/client/package.json @@ -4,6 +4,9 @@ "license": "MPL-2.0", "type": "module", "babel": { + "generatorOpts": { + "importAttributesKeyword": "with" + }, "presets": [ "react-app" ] diff --git a/client/src/assets/curriculum/landing-scrim.png b/client/src/assets/curriculum/landing-scrim.png new file mode 100644 index 000000000000..26929f273315 Binary files /dev/null and b/client/src/assets/curriculum/landing-scrim.png differ diff --git a/client/src/assets/curriculum/scrim-bg.png b/client/src/assets/curriculum/scrim-bg.png index 26929f273315..a88a6fab20f4 100644 Binary files a/client/src/assets/curriculum/scrim-bg.png and b/client/src/assets/curriculum/scrim-bg.png differ diff --git a/client/src/assets/curriculum/scrim-hexagons.svg b/client/src/assets/curriculum/scrim-hexagons.svg new file mode 100644 index 000000000000..3e9a2a7edccc --- /dev/null +++ b/client/src/assets/curriculum/scrim-hexagons.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/curriculum/scrim-play.svg b/client/src/assets/curriculum/scrim-play.svg index 1c58caf801bb..6e9f3095da1c 100644 --- a/client/src/assets/curriculum/scrim-play.svg +++ b/client/src/assets/curriculum/scrim-play.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/client/src/assets/curriculum/scrimba-logo.svg b/client/src/assets/curriculum/scrimba-logo.svg new file mode 100644 index 000000000000..8d33b4f84f3f --- /dev/null +++ b/client/src/assets/curriculum/scrimba-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/fonts/BarlowCondensed-SemiBold.woff2 b/client/src/assets/fonts/BarlowCondensed-SemiBold.woff2 new file mode 100644 index 000000000000..82dc38e001cf Binary files /dev/null and b/client/src/assets/fonts/BarlowCondensed-SemiBold.woff2 differ diff --git a/client/src/curriculum/index.scss b/client/src/curriculum/index.scss index 6f1eedda36d3..5c6d2df369b6 100644 --- a/client/src/curriculum/index.scss +++ b/client/src/curriculum/index.scss @@ -289,9 +289,15 @@ } scrim-inline { + aspect-ratio: 1.5; display: block; - height: 14.25rem; - width: 22rem; + margin: 0.5rem auto; + max-width: 36rem; + width: 100%; + } + + p:has(> scrim-inline:only-child) { + margin: 1rem 0; } } } diff --git a/client/src/curriculum/landing.tsx b/client/src/curriculum/landing.tsx index f621777d6e06..b6d36e9808c5 100644 --- a/client/src/curriculum/landing.tsx +++ b/client/src/curriculum/landing.tsx @@ -16,6 +16,7 @@ import "./landing.scss"; import { ProseSection } from "../../../libs/types/document"; import { PartnerBanner } from "./partner-banner"; import { useIsServer } from "../hooks"; +import scrimBg from "../assets/curriculum/landing-scrim.png"; const ScrimInline = lazy(() => import("./scrim-inline")); @@ -141,7 +142,15 @@ function About({ section }) {
- {!isServer && } + + {!isServer && ( + + )} +

Learn our curriculum with Scrimba's interactive{" "} diff --git a/client/src/curriculum/scrim-inline.global.css b/client/src/curriculum/scrim-inline.global.css new file mode 100644 index 000000000000..ac55e7fa316a --- /dev/null +++ b/client/src/curriculum/scrim-inline.global.css @@ -0,0 +1,8 @@ +@font-face { + font-family: "BarlowCondensed-SemiBold"; + font-weight: 600; + font-display: block; + src: + local("BarlowCondensed-SemiBold"), + url("../assets/fonts/BarlowCondensed-SemiBold.woff2") format("woff2"); +} diff --git a/client/src/curriculum/scrim-inline.scss b/client/src/curriculum/scrim-inline.scss new file mode 100644 index 000000000000..6618fb42ac2f --- /dev/null +++ b/client/src/curriculum/scrim-inline.scss @@ -0,0 +1,184 @@ +:host { + display: block; + overflow: hidden; +} + +* { + box-sizing: border-box; +} + +.visually-hidden { + border: 0 !important; + clip: rect(1px, 1px, 1px, 1px) !important; + -webkit-clip-path: inset(50%) !important; + clip-path: inset(50%) !important; + height: 1px !important; + margin: -1px !important; + overflow: hidden !important; + padding: 0 !important; + position: absolute !important; + white-space: nowrap !important; + width: 1px !important; +} + +button { + appearance: none; + background: none; + border: none; + padding: 0; +} + +dialog { + display: contents; + + &[open] { + background-color: #0009; + height: 90vh; + width: 90vw; + } +} + +.inner { + background-color: #000; + border: 1px solid #000; + container-type: size; + display: flex; + flex-direction: column; + height: 100%; + width: 100%; +} + +.header { + align-items: center; + background: #000; + display: flex; + gap: 0.25rem; + margin: 0; + min-height: 1.75rem; + padding: 0 0.5rem; + width: 100%; + + span { + color: #fff; + font-size: var(--type-tiny-font-size); + margin-right: auto; + } +} + +.toggle, +.external { + background-color: #fff; + cursor: pointer; + height: 1rem; + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + width: 1rem; + + &:hover { + background-color: var(--curriculum-color); + } + + &:focus-visible { + outline-color: var(--accent-primary); + outline-offset: 1px; + outline-style: auto; + } +} + +.toggle { + &.enter { + mask-image: url("../assets/icons/fullscreen-enter.svg"); + } + + &.exit { + mask-image: url("../assets/icons/cancel.svg"); + } +} + +.external { + mask-image: url("../assets/icons/external.svg"); + mask-size: 75%; +} + +.body { + flex: 1; + font-size: 4cqmin; + position: relative; +} + +.background { + background-color: #453c78; + background-image: url("../assets/curriculum/scrimba-logo.svg"), + url("../assets/curriculum/scrim-hexagons.svg"), + url("../assets/curriculum/scrim-bg.png"); + background-position: + 1.5em 1.5em, + right, + center; + background-repeat: no-repeat; + background-size: + auto 0.6em, + contain, + cover; + inset: 0; + position: absolute; + + h1 { + bottom: 0; + color: #fff; + font-family: "BarlowCondensed-SemiBold", "Inter", sans-serif; + font-size: 3em; + font-weight: 600; + left: 0; + line-height: 1; + margin: 0.5em; + position: absolute; + text-transform: uppercase; + text-wrap: balance; + width: 60%; + } +} + +.background-noise { + filter: url("#noise"); + inset: 0; + mix-blend-mode: soft-light; + position: absolute; +} + +.open, +iframe { + border: none; + height: 100%; + position: absolute; + width: 100%; +} + +.open { + --color: #8cb4ffcc; + background-image: var(--scrim-img); + background-position: center; + background-repeat: no-repeat; + background-size: cover; + cursor: pointer; + font-size: inherit; + + &:hover { + --color: #8cb4ffe5; + } + + svg { + height: 9em; + stroke-width: 2px; + width: auto; + + circle { + fill: var(--color); + } + + path { + fill: #fff; + } + } +} diff --git a/client/src/curriculum/scrim-inline.ts b/client/src/curriculum/scrim-inline.ts index 8696a977d476..196a88ee6204 100644 --- a/client/src/curriculum/scrim-inline.ts +++ b/client/src/curriculum/scrim-inline.ts @@ -1,15 +1,14 @@ -import { css, html, LitElement, PropertyValues, unsafeCSS } from "lit"; +import { html, LitElement, PropertyValues } from "lit"; import { customElement } from "lit/decorators.js"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { StyleInfo, styleMap } from "lit/directives/style-map.js"; +import { ifDefined } from "lit/directives/if-defined.js"; import { createComponent } from "@lit/react"; import React from "react"; import { CURRICULUM } from "../telemetry/constants"; -import externalIcon from "../assets/icons/external.svg?url"; -import fullscreenIcon from "../assets/icons/fullscreen-enter.svg?url"; -import cancelIcon from "../assets/icons/cancel.svg?url"; -import backgroundImg from "../assets/curriculum/scrim-bg.png"; +import "./scrim-inline.global.css"; +import styles from "./scrim-inline.scss?css" with { type: "css" }; import playSvg from "../assets/curriculum/scrim-play.svg?raw"; @customElement("scrim-inline") @@ -21,146 +20,20 @@ class ScrimInline extends LitElement { img?: string; _imgStyle: StyleInfo = {}; + scrimTitle?: string; + _fullscreen = false; _scrimLoaded = false; static properties = { url: { type: String }, img: { type: String }, + scrimTitle: { type: String }, _fullscreen: { state: true }, _scrimLoaded: { state: true }, }; - static styles = css` - * { - box-sizing: border-box; - } - - .visually-hidden { - border: 0 !important; - clip: rect(1px, 1px, 1px, 1px) !important; - -webkit-clip-path: inset(50%) !important; - clip-path: inset(50%) !important; - height: 1px !important; - margin: -1px !important; - overflow: hidden !important; - padding: 0 !important; - position: absolute !important; - white-space: nowrap !important; - width: 1px !important; - } - - button { - appearance: none; - background: none; - border: none; - padding: 0; - } - - dialog { - display: contents; - - &[open] { - background-color: #0009; - height: 90vh; - width: 90vw; - } - } - - .inner { - background-color: #000; - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - } - - .header { - align-items: center; - display: flex; - gap: 0.25rem; - margin: 0; - padding: 0 0.5rem; - width: 100%; - min-height: 1.75rem; - - span { - color: #fff; - font-size: var(--type-tiny-font-size); - margin-right: auto; - } - } - - .toggle, - .external { - background-color: #fff; - cursor: pointer; - height: 1rem; - width: 1rem; - mask-size: contain; - mask-position: center; - mask-repeat: no-repeat; - - &:hover { - background-color: var(--curriculum-color); - } - - &:focus-visible { - outline-color: var(--accent-primary); - outline-offset: 1px; - outline-style: auto; - } - } - - .toggle { - &.enter { - mask-image: url(${unsafeCSS(fullscreenIcon)}); - } - - &.exit { - mask-image: url(${unsafeCSS(cancelIcon)}); - } - } - - .external { - mask-image: url(${unsafeCSS(externalIcon)}); - mask-size: 75%; - } - - .open, - iframe { - border: 1px solid #000; - width: 100%; - height: 100%; - } - - .open { - --color: #8cb4ffcc; - cursor: pointer; - background-image: var(--img, url(${unsafeCSS(backgroundImg)})); - background-repeat: no-repeat; - background-position: center; - background-size: cover; - - &:hover { - --color: #8cb4ffe5; - } - - svg { - height: 7rem; - width: 7rem; - stroke-width: 2px; - - circle { - fill: var(--color); - } - - path { - fill: #fff; - } - } - } - `; + static styles = styles; willUpdate(changedProperties: PropertyValues) { if (changedProperties.has("url")) { @@ -179,7 +52,7 @@ class ScrimInline extends LitElement { if (changedProperties.has("img")) { this._imgStyle = this.img ? { - "--img": `url(${this.img})`, + "--scrim-img": `url(${this.img})`, } : {}; } @@ -211,25 +84,43 @@ class ScrimInline extends LitElement { Open on Scrimba - ${this._scrimLoaded - ? html` - - ` - : html` - - `} +

+ ${this._scrimLoaded + ? html` + + ` + : html` + ${this.scrimTitle && !this.img + ? html`
+
+ + + + + +
+

${this.scrimTitle}

+
` + : null} + + `} +
`; diff --git a/client/src/react-app.d.ts b/client/src/react-app.d.ts index 5d38e0818e80..9c88c30b8449 100644 --- a/client/src/react-app.d.ts +++ b/client/src/react-app.d.ts @@ -79,14 +79,17 @@ declare module "*.svg" { export default src; } -declare module "*?url" { +declare module "*?raw" { const src: string; export default src; } -declare module "*?raw" { - const src: string; - export default src; +// once https://github.com/microsoft/TypeScript/issues/46135 is fixed +// we'll be able to do something like: +// declare module '*' with {type: 'css'} { +declare module "*?css" { + const sheet: CSSStyleSheet; + export default sheet; } declare module "*.module.css" { diff --git a/client/tsconfig.json b/client/tsconfig.json index 3785472bddfd..9d382647f7ae 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -7,7 +7,7 @@ "isolatedModules": true, "jsx": "react-jsx", "lib": ["dom", "dom.iterable", "es2021"], - "module": "ES2020", + "module": "ESNext", "moduleResolution": "Node", "noEmit": true, "noFallthroughCasesInSwitch": true, diff --git a/ssr/react-app.d.ts b/ssr/react-app.d.ts index 31c25c28c1fc..2a9a1edb7dbe 100644 --- a/ssr/react-app.d.ts +++ b/ssr/react-app.d.ts @@ -75,14 +75,17 @@ declare module "*.svg" { export default src; } -declare module "*?url" { +declare module "*?raw" { const src: string; export default src; } -declare module "*?raw" { - const src: string; - export default src; +// once https://github.com/microsoft/TypeScript/issues/46135 is fixed +// we'll be able to do something like: +// declare module '*' with {type: 'css'} { +declare module "*?css" { + const sheet: CSSStyleSheet; + export default sheet; } declare module "*.module.css" { diff --git a/ssr/webpack.config.js b/ssr/webpack.config.js index 5bbcb178f073..d0817c888a3c 100644 --- a/ssr/webpack.config.js +++ b/ssr/webpack.config.js @@ -98,10 +98,6 @@ const config = { resourceQuery: /raw/, type: "asset/source", }, - { - resourceQuery: /url/, - type: "asset/resource", - }, { test: /\.tsx?$/, exclude: /node_modules/,