mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-12-12 00:31:11 -05:00
Merge remote-tracking branch 'origin/integration-branch-ux-12.2024' into UEPR-88
This commit is contained in:
commit
44295ea514
14 changed files with 527 additions and 94 deletions
|
@ -33,6 +33,7 @@ $ui-cyan-blue: hsla(194, 73%, 36%, 1); //#19809F
|
||||||
/* Using www naming convention for now, should be consistent with gui */
|
/* Using www naming convention for now, should be consistent with gui */
|
||||||
$ui-aqua: hsla(144, 45%, 36%, 1);
|
$ui-aqua: hsla(144, 45%, 36%, 1);
|
||||||
$ui-aqua-dark: darken($ui-aqua, 10%);
|
$ui-aqua-dark: darken($ui-aqua, 10%);
|
||||||
|
$ui-purple-light: hsla(260, 100%, 88%, 1); // #DACEF3
|
||||||
$ui-purple: hsla(260, 100%, 70%, 1); // #9966FF Looks Primary
|
$ui-purple: hsla(260, 100%, 70%, 1); // #9966FF Looks Primary
|
||||||
$ui-purple-dark: hsla(260, 60%, 60%, 1); // #855CD6 Looks Secondary
|
$ui-purple-dark: hsla(260, 60%, 60%, 1); // #855CD6 Looks Secondary
|
||||||
$ui-purple-darker: hsla(260, 46%, 54%, 1);
|
$ui-purple-darker: hsla(260, 46%, 54%, 1);
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import api from '../../lib/api';
|
||||||
|
import {YoutubeVideoButton} from '../youtube-video-button/youtube-video-button.jsx';
|
||||||
|
import Spinner from '../spinner/spinner.jsx';
|
||||||
|
|
||||||
|
import './youtube-playlist-item.scss';
|
||||||
|
|
||||||
|
export const YoutubePlaylistItem = ({playlistRequestUri, playlistTitleId, onSelectedVideo}) => {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [playlistVideos, setPlaylistVideos] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
api({
|
||||||
|
host: process.env.ROOT_URL,
|
||||||
|
method: 'GET',
|
||||||
|
uri: playlistRequestUri,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}, (_err, body, res) => {
|
||||||
|
setLoading(false);
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
setPlaylistVideos(body);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="playlist">
|
||||||
|
<div className="playlist-title">
|
||||||
|
<FormattedMessage id={playlistTitleId} />
|
||||||
|
</div>
|
||||||
|
{loading ? (
|
||||||
|
<Spinner
|
||||||
|
className="spinner"
|
||||||
|
color="transparent-gray"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<section className="playlist-videos">
|
||||||
|
{playlistVideos
|
||||||
|
.map(video => (
|
||||||
|
<YoutubeVideoButton
|
||||||
|
key={video.videoId}
|
||||||
|
onSelectedVideo={onSelectedVideo}
|
||||||
|
{...video}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
YoutubePlaylistItem.propTypes = {
|
||||||
|
playlistRequestUri: PropTypes.string,
|
||||||
|
playlistTitleId: PropTypes.string,
|
||||||
|
onSelectedVideo: PropTypes.func
|
||||||
|
};
|
|
@ -0,0 +1,26 @@
|
||||||
|
.playlist {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
.playlist-title {
|
||||||
|
display: flex;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 2.5rem;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist-videos {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(13.125rem, auto));
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: start;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
}
|
45
src/components/youtube-video-button/youtube-video-button.jsx
Normal file
45
src/components/youtube-video-button/youtube-video-button.jsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import './youtube-video-button.scss';
|
||||||
|
|
||||||
|
const parseViewCount = viewCount =>
|
||||||
|
parseInt(viewCount, 10).toLocaleString('en-US', {
|
||||||
|
notation: 'compact',
|
||||||
|
compactDisplay: 'short'
|
||||||
|
});
|
||||||
|
|
||||||
|
export const YoutubeVideoButton = ({onSelectedVideo, ...videoData}) => (
|
||||||
|
<div
|
||||||
|
className="youtbe-video-button"
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => onSelectedVideo(videoData.videoId)}
|
||||||
|
>
|
||||||
|
<div className="thumbnail">
|
||||||
|
<img src={videoData.thumbnail} />
|
||||||
|
<div className="duration">{videoData.length}</div>
|
||||||
|
</div>
|
||||||
|
<div className="video-info">
|
||||||
|
<div className="video-title">{videoData.title}</div>
|
||||||
|
<div className="video-metadata">
|
||||||
|
<div className="channel-name">{videoData.channel}</div>
|
||||||
|
<div className="video-statistics">
|
||||||
|
{`${parseViewCount(videoData.views)} · ${videoData.uploadTime}`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{videoData.hasCC && <img src="/images/ideas/video-cc-label.svg" />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
YoutubeVideoButton.propTypes = {
|
||||||
|
videoId: PropTypes.string,
|
||||||
|
title: PropTypes.string,
|
||||||
|
thumbnail: PropTypes.string,
|
||||||
|
channel: PropTypes.string,
|
||||||
|
uploadTime: PropTypes.string,
|
||||||
|
length: PropTypes.string,
|
||||||
|
views: PropTypes.string,
|
||||||
|
hasCC: PropTypes.bool,
|
||||||
|
onSelectedVideo: PropTypes.func
|
||||||
|
};
|
|
@ -0,0 +1,70 @@
|
||||||
|
@import "../../colors";
|
||||||
|
|
||||||
|
.youtbe-video-button {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
|
||||||
|
width: 13.125rem;
|
||||||
|
|
||||||
|
.thumbnail {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
bottom : 0.5rem;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
margin: 0.5rem;
|
||||||
|
padding: 0.25rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: $overlay-gray;
|
||||||
|
color: $type-white;
|
||||||
|
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-info {
|
||||||
|
margin-right: 1.5rem;
|
||||||
|
text-align: start;
|
||||||
|
|
||||||
|
.video-title {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-height: 1.2rem;
|
||||||
|
max-height: 2.4rem;
|
||||||
|
|
||||||
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-metadata {
|
||||||
|
margin: 0.5rem 4rem 0.25rem 0;
|
||||||
|
|
||||||
|
color: $ui-dark-gray;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.125rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
47
src/components/youtube-video-modal/youtube-video-modal.jsx
Normal file
47
src/components/youtube-video-modal/youtube-video-modal.jsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactModal from 'react-modal';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Button from '../forms/button.jsx';
|
||||||
|
const classNames = require('classnames');
|
||||||
|
|
||||||
|
import './youtube-video-modal.scss';
|
||||||
|
|
||||||
|
export const YoutubeVideoModal = ({videoId, onClose = () => {}, className}) => {
|
||||||
|
if (!videoId) return null;
|
||||||
|
return (
|
||||||
|
<ReactModal
|
||||||
|
isOpen={!!videoId}
|
||||||
|
onRequestClose={onClose}
|
||||||
|
className="youtube-video-modal-container"
|
||||||
|
overlayClassName="youtube-video-modal-overlay"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames('cards-modal-header', className)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="close-button"
|
||||||
|
isCloseType
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<iframe
|
||||||
|
className="youtube-player"
|
||||||
|
type="text/html"
|
||||||
|
width="640"
|
||||||
|
height="360"
|
||||||
|
src={`https://www.youtube.com/embed/${videoId}?rel=0&cc_load_policy=1&autoplay=1`}
|
||||||
|
allow="autoplay"
|
||||||
|
/>
|
||||||
|
</ReactModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
YoutubeVideoModal.defaultProps = {
|
||||||
|
className: 'mint-green'
|
||||||
|
};
|
||||||
|
|
||||||
|
YoutubeVideoModal.propTypes = {
|
||||||
|
videoId: PropTypes.string,
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
className: PropTypes.string
|
||||||
|
};
|
60
src/components/youtube-video-modal/youtube-video-modal.scss
Normal file
60
src/components/youtube-video-modal/youtube-video-modal.scss
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
@import "../../colors";
|
||||||
|
|
||||||
|
.youtube-video-modal-overlay {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 510;
|
||||||
|
|
||||||
|
background-color: $box-shadow-light-gray;
|
||||||
|
border-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.youtube-video-modal-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 640px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards-modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
position: unset;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mint-green {
|
||||||
|
background-color: $ui-mint-green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.youtube-player {
|
||||||
|
border: 0;
|
||||||
|
border-bottom-left-radius: 8px;
|
||||||
|
border-bottom-right-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.youtube-video-modal-container:focus {
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
}
|
|
@ -4,12 +4,15 @@ const {useState, useCallback} = require('react');
|
||||||
|
|
||||||
const Button = require('../../components/forms/button.jsx');
|
const Button = require('../../components/forms/button.jsx');
|
||||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||||
const TitleBanner = require('../../components/title-banner/title-banner.jsx');
|
|
||||||
|
|
||||||
const Page = require('../../components/page/www/page.jsx');
|
const Page = require('../../components/page/www/page.jsx');
|
||||||
const render = require('../../lib/render.jsx');
|
const render = require('../../lib/render.jsx');
|
||||||
|
|
||||||
const {useIntl} = require('react-intl');
|
const {useIntl} = require('react-intl');
|
||||||
|
const {
|
||||||
|
YoutubeVideoModal
|
||||||
|
} = require('../../components/youtube-video-modal/youtube-video-modal.jsx');
|
||||||
|
const {YoutubePlaylistItem} = require('../../components/youtube-playlist-item/youtube-playlist-item.jsx');
|
||||||
const {CardsModal} = require('../../components/cards-modal/cards-modal.jsx');
|
const {CardsModal} = require('../../components/cards-modal/cards-modal.jsx');
|
||||||
|
|
||||||
require('./ideas.scss');
|
require('./ideas.scss');
|
||||||
|
@ -87,46 +90,51 @@ const physicalIdeasData = [
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const playlists = {
|
||||||
|
'sprites-and-vectors': 'ideas.spritesAndVector',
|
||||||
|
'tips-and-tricks': 'ideas.tipsAndTricks',
|
||||||
|
'advanced-topics': 'ideas.advancedTopics'
|
||||||
|
};
|
||||||
|
|
||||||
const Ideas = () => {
|
const Ideas = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [youtubeVideoId, setYoutubeVideoId] = useState('');
|
||||||
|
const [isCardsModalOpen, setCardsModalOpen] = useState(false);
|
||||||
|
|
||||||
const onOpen = useCallback(() => setIsOpen(true), [setIsOpen]);
|
const onCloseVideoModal = useCallback(() => setYoutubeVideoId(''), [setYoutubeVideoId]);
|
||||||
const onClose = useCallback(() => setIsOpen(false), [setIsOpen]);
|
const onSelectedVideo = useCallback(
|
||||||
|
videoId => setYoutubeVideoId(videoId),
|
||||||
|
[setYoutubeVideoId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onCardsModalOpen = useCallback(() => setCardsModalOpen(true), [isCardsModalOpen]);
|
||||||
|
const onCardsModalClose = useCallback(() => setCardsModalOpen(false), [isCardsModalOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="banner-wrapper">
|
<div className="banner-wrapper">
|
||||||
<TitleBanner className="masthead ideas-banner">
|
|
||||||
<div className="title-banner-p">
|
|
||||||
<img
|
<img
|
||||||
alt={intl.formatMessage({id: 'ideas.headerImageDescription'})}
|
alt={intl.formatMessage({id: 'ideas.headerImageDescription'})}
|
||||||
src="/images/ideas/masthead-illustration.svg"
|
src="/images/ideas/banner.svg"
|
||||||
/>
|
/>
|
||||||
<h1 className="title-banner-h1">
|
<div className="banner-description">
|
||||||
<FormattedMessage id="ideas.headerMessage" />
|
<div className="title">
|
||||||
</h1>
|
<FormattedMessage id="ideas.headerTitle" />
|
||||||
<a href="/projects/editor/?tutorial=all">
|
</div>
|
||||||
<Button className="banner-button">
|
<p>
|
||||||
<img
|
<FormattedMessage
|
||||||
alt=""
|
id="ideas.headerDescription"
|
||||||
src="/images/ideas/bulb-yellow-icon.svg"
|
values={{a: chunks => <a href="https://scratch.mit.edu/projects/1093752362/">{chunks}</a>}}
|
||||||
/>
|
/>
|
||||||
<FormattedMessage id="ideas.headerButtonMessage" />
|
</p>
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</TitleBanner>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="tips">
|
<div className="tips">
|
||||||
<div className="inner">
|
<div className="inner">
|
||||||
<div className="section-header">
|
<div className="section-header">
|
||||||
<FormattedMessage id="ideas.startHereText" />
|
<FormattedMessage id="ideas.startHereText" />
|
||||||
</div>
|
</div>
|
||||||
<FlexRow
|
<section className="tips-section">
|
||||||
as="section"
|
|
||||||
className="tips-section"
|
|
||||||
>
|
|
||||||
{tipsSectionData.map((tipData, index) => (
|
{tipsSectionData.map((tipData, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
|
@ -160,17 +168,51 @@ const Ideas = () => {
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</FlexRow>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="youtube-videos">
|
<div className="youtube-videos">
|
||||||
<div className="inner">
|
<div className="inner">
|
||||||
|
<div className="section-header">
|
||||||
|
<img src="/images/ideas/youtube-icon.svg" />
|
||||||
|
<div>
|
||||||
|
<div className="section-title">
|
||||||
|
<FormattedMessage id="ideas.scratchYouTubeChannel" />
|
||||||
|
</div>
|
||||||
|
<div className="section-description">
|
||||||
|
<FormattedMessage
|
||||||
|
id="ideas.scratchYouTubeChannelDescription"
|
||||||
|
values={{
|
||||||
|
a: chunks => (
|
||||||
|
<a href="https://www.youtube.com/@ScratchTeam">
|
||||||
|
{chunks}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<section className="playlists">
|
||||||
|
{Object.keys(playlists).map(playlistKey => (
|
||||||
|
<YoutubePlaylistItem
|
||||||
|
key={playlistKey}
|
||||||
|
playlistRequestUri={`/ideas/videos/${playlistKey}`}
|
||||||
|
playlistTitleId={playlists[playlistKey]}
|
||||||
|
onSelectedVideo={onSelectedVideo}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<YoutubeVideoModal
|
||||||
|
videoId={youtubeVideoId}
|
||||||
|
onClose={onCloseVideoModal}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
<div
|
<div
|
||||||
className="download-cards"
|
className="download-cards"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="pass"
|
className="pass"
|
||||||
onClick={onOpen}
|
onClick={onCardsModalOpen}
|
||||||
>
|
>
|
||||||
<img src="/images/ideas/download-icon.svg" />
|
<img src="/images/ideas/download-icon.svg" />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -178,12 +220,12 @@ const Ideas = () => {
|
||||||
id="ideas.downloadGuides"
|
id="ideas.downloadGuides"
|
||||||
values={{
|
values={{
|
||||||
strong: chunks => <strong>{chunks}</strong>,
|
strong: chunks => <strong>{chunks}</strong>,
|
||||||
a: chunks => <a onClick={onOpen}>{chunks}</a>
|
a: chunks => <a onClick={onCardsModalOpen}>{chunks}</a>
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<CardsModal
|
<CardsModal
|
||||||
isOpen={isOpen}
|
isOpen={isCardsModalOpen}
|
||||||
onClose={onClose}
|
onClose={onCardsModalClose}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -231,7 +273,9 @@ const Ideas = () => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id={physicalIdea.physicalIdeasDescription.buttonTextId}
|
id={
|
||||||
|
physicalIdea.physicalIdeasDescription.buttonTextId
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -9,12 +9,40 @@ $base-bg: $ui-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.banner-wrapper {
|
.banner-wrapper {
|
||||||
background: $ui-aqua bottom right url("/images/ideas/right-juice.png") no-repeat;
|
display: flex;
|
||||||
}
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2rem;
|
||||||
|
padding: 3rem 0;
|
||||||
|
background-color: $ui-aqua;
|
||||||
|
|
||||||
.ideas-banner {
|
.banner-description {
|
||||||
margin-bottom: 0;
|
display: flex;
|
||||||
background: bottom left url("/images/ideas/left-juice.png") no-repeat;
|
flex-direction: column;
|
||||||
|
text-align: start;
|
||||||
|
max-width: 27rem;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 2.5rem;
|
||||||
|
color: $ui-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
color: $ui-white;
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 700;
|
||||||
|
text-decoration: underline;
|
||||||
|
color: $ui-purple-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tips {
|
.tips {
|
||||||
|
@ -24,8 +52,10 @@ $base-bg: $ui-white;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
|
|
||||||
.tips-section {
|
.tips-section {
|
||||||
display: flex;
|
display: grid;
|
||||||
justify-content: space-between;
|
grid-template-columns: repeat(auto-fill, minmax($cols3, auto));
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.tip {
|
.tip {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -42,6 +72,52 @@ $base-bg: $ui-white;
|
||||||
|
|
||||||
.youtube-videos {
|
.youtube-videos {
|
||||||
.inner {
|
.inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 2.5rem;
|
||||||
|
padding: 4rem 0;
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
max-width: 25rem;
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 2.5rem;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-description {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
text-align: start;
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 400;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlists {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
.download-cards {
|
.download-cards {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -63,6 +139,7 @@ $base-bg: $ui-white;
|
||||||
text-align: start;
|
text-align: start;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
font-weight: 400;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,10 +231,14 @@ $base-bg: $ui-white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header {
|
.tips, .physical-ideas {
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 2.5rem;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tips-button {
|
.tips-button {
|
||||||
|
@ -166,7 +247,6 @@ $base-bg: $ui-white;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
|
||||||
font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.25rem;
|
line-height: 1.25rem;
|
||||||
|
|
||||||
|
@ -178,29 +258,16 @@ $base-bg: $ui-white;
|
||||||
|
|
||||||
//4 columns
|
//4 columns
|
||||||
@media #{$small} {
|
@media #{$small} {
|
||||||
|
.banner-wrapper {
|
||||||
.title-banner {
|
img {
|
||||||
&.masthead {
|
display: none;
|
||||||
padding-bottom: 1.25rem;
|
|
||||||
|
|
||||||
p {
|
|
||||||
max-width: $cols4;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// put the image first if in 4-column
|
|
||||||
.tips-info-body {
|
.tips-info-body {
|
||||||
max-width: $cols4;
|
max-width: $cols4;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
&.tips-illustration {
|
|
||||||
order: -1;
|
|
||||||
img {
|
|
||||||
width: $cols4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -209,34 +276,14 @@ $base-bg: $ui-white;
|
||||||
|
|
||||||
//6 columns
|
//6 columns
|
||||||
@media #{$medium} {
|
@media #{$medium} {
|
||||||
.title-banner {
|
|
||||||
&.masthead {
|
|
||||||
|
|
||||||
p {
|
|
||||||
max-width: $cols6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tips-info-body {
|
.tips-info-body {
|
||||||
max-width: $cols4;
|
max-width: $cols4;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//8 columns
|
//8 columns
|
||||||
@media #{$intermediate} {
|
@media #{$intermediate} {
|
||||||
.title-banner {
|
|
||||||
&.masthead {
|
|
||||||
padding-bottom: 2rem;
|
|
||||||
|
|
||||||
p {
|
|
||||||
max-width: $cols6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tips-info-body {
|
.tips-info-body {
|
||||||
max-width: $cols4;
|
max-width: $cols4;
|
||||||
}
|
}
|
||||||
|
@ -256,16 +303,6 @@ $base-bg: $ui-white;
|
||||||
|
|
||||||
// 12 columns
|
// 12 columns
|
||||||
@media #{$big} {
|
@media #{$big} {
|
||||||
.title-banner {
|
|
||||||
&.masthead {
|
|
||||||
padding-bottom: 1.25rem;
|
|
||||||
|
|
||||||
p {
|
|
||||||
max-width: $cols8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tips-info-section {
|
.tips-info-section {
|
||||||
&.mod-align-top {
|
&.mod-align-top {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"ideas.headerMessage": "What will you create?",
|
"ideas.headerTitle": "Looking for a project idea?",
|
||||||
"ideas.headerImageDescription": "Outlandish creations from pixelated unicorns to drumbeat waveforms to levitating tacos to buckets of rainbows.",
|
"ideas.headerDescription": "Try Scratch’s Project Idea Generator! Pick as many ideas as you’d like. Mix and match ideas! <a>Remix your own</a> idea generator! The possibilities are endless.",
|
||||||
|
"ideas.headerImageDescription": "Scratch cat holding a lightning bulb and a block",
|
||||||
"ideas.headerButtonMessage": "Choose a tutorial",
|
"ideas.headerButtonMessage": "Choose a tutorial",
|
||||||
"ideas.startHereText": "New to Scratch? Start here!",
|
"ideas.startHereText": "New to Scratch? Start here!",
|
||||||
"ideas.gettingStartedButtonText": "Try Getting Started Tutorial",
|
"ideas.gettingStartedButtonText": "Try Getting Started Tutorial",
|
||||||
|
@ -31,6 +32,11 @@
|
||||||
"ideas.tryTheTutorial": "Try the tutorial",
|
"ideas.tryTheTutorial": "Try the tutorial",
|
||||||
"ideas.codingCards": "Coding Cards",
|
"ideas.codingCards": "Coding Cards",
|
||||||
"ideas.educatorGuide": "Educator Guide",
|
"ideas.educatorGuide": "Educator Guide",
|
||||||
|
"ideas.scratchYouTubeChannel": "ScratchTeam channel",
|
||||||
|
"ideas.scratchYouTubeChannelDescription": "This is the official <a>Youtube Channel</a> of Scratch. We share resources, tutorials, and stories about Scratch.",
|
||||||
|
"ideas.spritesAndVector": "Sprites & Vector Drawing",
|
||||||
|
"ideas.tipsAndTricks": "Tips & Tricks",
|
||||||
|
"ideas.advancedTopics": "Advanced Topics",
|
||||||
"ideas.physicalPlayIdeas": "Physical Play Ideas",
|
"ideas.physicalPlayIdeas": "Physical Play Ideas",
|
||||||
"ideas.microBitHeader": "Have a micro:bit?",
|
"ideas.microBitHeader": "Have a micro:bit?",
|
||||||
"ideas.microBitBody": "Connect your Scratch project to the real world.",
|
"ideas.microBitBody": "Connect your Scratch project to the real world.",
|
||||||
|
|
19
static/images/ideas/banner.svg
Normal file
19
static/images/ideas/banner.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 370 KiB |
4
static/images/ideas/video-cc-label.svg
Normal file
4
static/images/ideas/video-cc-label.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="18" height="17" viewBox="0 0 18 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="2" y="0.0300293" width="15" height="16" fill="#606060"/>
|
||||||
|
<path d="M15 7.03003H13.5V6.53003H11.5V9.53003H13.5V9.03003H15V10.03C15 10.2952 14.8946 10.5496 14.7071 10.7371C14.5196 10.9247 14.2652 11.03 14 11.03H11C10.7348 11.03 10.4804 10.9247 10.2929 10.7371C10.1054 10.5496 10 10.2952 10 10.03V6.03003C10 5.76481 10.1054 5.51046 10.2929 5.32292C10.4804 5.13539 10.7348 5.03003 11 5.03003H14C14.2652 5.03003 14.5196 5.13539 14.7071 5.32292C14.8946 5.51046 15 5.76481 15 6.03003M8 7.03003H6.5V6.53003H4.5V9.53003H6.5V9.03003H8V10.03C8 10.2952 7.89464 10.5496 7.70711 10.7371C7.51957 10.9247 7.26522 11.03 7 11.03H4C3.73478 11.03 3.48043 10.9247 3.29289 10.7371C3.10536 10.5496 3 10.2952 3 10.03V6.03003C3 5.76481 3.10536 5.51046 3.29289 5.32292C3.48043 5.13539 3.73478 5.03003 4 5.03003H7C7.26522 5.03003 7.51957 5.13539 7.70711 5.32292C7.89464 5.51046 8 5.76481 8 6.03003M16 0.0300293H2C0.89 0.0300293 0 0.920029 0 2.03003V14.03C0 14.5605 0.210714 15.0692 0.585786 15.4442C0.960859 15.8193 1.46957 16.03 2 16.03H16C16.5304 16.03 17.0391 15.8193 17.4142 15.4442C17.7893 15.0692 18 14.5605 18 14.03V2.03003C18 1.4996 17.7893 0.990888 17.4142 0.615816C17.0391 0.240743 16.5304 0.0300293 16 0.0300293Z" fill="#F4F4F4"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
12
static/images/ideas/youtube-icon.svg
Normal file
12
static/images/ideas/youtube-icon.svg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<svg width="140" height="110" viewBox="0 0 140 110" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_2_749)">
|
||||||
|
<path opacity="0.15" fill-rule="evenodd" clip-rule="evenodd" d="M55 102.36C23.62 102.06 -6.70002 64.7 2.64998 39.56C12.01 14.43 29.31 -0.300021 60.69 -2.09711e-05C92.07 0.299979 118.24 30.85 123.1 63.38C127.97 95.91 86.38 102.65 55 102.35V102.36ZM22.16 108.18C17.61 111.53 10.57 104.29 6.26998 99.23C1.96998 94.17 3.61998 88.8 7.47998 88.84C10.12 88.87 12.94 91.03 15.54 93.07L15.79 93.27C16.89 94.13 17.95 94.96 18.93 95.57C22.3 97.67 26.71 104.83 22.16 108.18Z" fill="#628EC9"/>
|
||||||
|
<path d="M139.01 57.4C138.99 65.5 133.84 71.28 126.28 73.12C118.11 75.11 111.03 76.27 93.01 78.16C74.39 80.12 67.76 80.36 59.32 80.15C51.29 79.95 45.15 75.44 43.6 67.42C43.28 65.76 41.85 60.53 40.91 51.64C40.24 45.22 40.47 38.9 40.44 37.34C40.3 29.32 45.38 23.67 53.17 21.62C61.33 19.47 67.42 18.32 86.51 16.31C105.55 14.31 111.72 14.38 120.13 14.58C128.33 14.78 134.34 19.13 135.85 27.31C136.14 28.9 137.59 34.09 138.44 42.16C139.31 50.45 139.01 55.76 139.01 57.39V57.4Z" fill="#FF0000"/>
|
||||||
|
<path d="M84.33 59.9L104.45 45.28L81.73 35.16L84.33 59.9Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_2_749">
|
||||||
|
<rect width="138.14" height="109.03" fill="white" transform="translate(0.929993)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -271,6 +271,7 @@ module.exports = {
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env.NODE_ENV': `"${process.env.NODE_ENV || 'development'}"`,
|
'process.env.NODE_ENV': `"${process.env.NODE_ENV || 'development'}"`,
|
||||||
'process.env.API_HOST': `"${process.env.API_HOST || 'https://api.scratch.mit.edu'}"`,
|
'process.env.API_HOST': `"${process.env.API_HOST || 'https://api.scratch.mit.edu'}"`,
|
||||||
|
'process.env.ROOT_URL': `"${process.env.ROOT_URL || 'https://scratch.mit.edu'}"`,
|
||||||
'process.env.RECAPTCHA_SITE_KEY': `"${
|
'process.env.RECAPTCHA_SITE_KEY': `"${
|
||||||
process.env.RECAPTCHA_SITE_KEY || '6Lf6kK4UAAAAABKTyvdSqgcSVASEnMrCquiAkjVW'}"`,
|
process.env.RECAPTCHA_SITE_KEY || '6Lf6kK4UAAAAABKTyvdSqgcSVASEnMrCquiAkjVW'}"`,
|
||||||
'process.env.ASSET_HOST': `"${process.env.ASSET_HOST || 'https://assets.scratch.mit.edu'}"`,
|
'process.env.ASSET_HOST': `"${process.env.ASSET_HOST || 'https://assets.scratch.mit.edu'}"`,
|
||||||
|
|
Loading…
Reference in a new issue