diff --git a/app.js b/app.js index e214889..931db1f 100644 --- a/app.js +++ b/app.js @@ -52,6 +52,7 @@ export default () => { data={data} loading={loading || !minLoadTimePassed} infoActive={infoActive} + // infoActive currentVideo={currentVideo} setInfoActive={setInfoActive} /> diff --git a/package.json b/package.json index 6cfd82d..93bde34 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "date-fns": "^2.19.0", "ical": "^0.8.0", "ical.js": "^1.4.0", + "markdown-to-jsx": "^7.1.2", "preact": "^10.5.12", "prop-types": "^15.7.2", "styled-components": "^5.2.1" @@ -31,6 +32,7 @@ "babel-preset-env": "^1.6.1", "eslint": "^4.12.1", "eslint-config-flying-rocket": "^1.1.1", + "marked": "^2.0.3", "module-alias": "^2.0.3", "parcel-bundler": "1.12.3", "prettier": "^1.9.1", diff --git a/src/assets/img/hero/1lg.png b/src/assets/img/hero/1lg.png new file mode 100644 index 0000000..7b03725 Binary files /dev/null and b/src/assets/img/hero/1lg.png differ diff --git a/src/assets/img/hero/1sm.png b/src/assets/img/hero/1sm.png new file mode 100644 index 0000000..958911b Binary files /dev/null and b/src/assets/img/hero/1sm.png differ diff --git a/src/assets/img/hero/2lg.png b/src/assets/img/hero/2lg.png new file mode 100644 index 0000000..816fa59 Binary files /dev/null and b/src/assets/img/hero/2lg.png differ diff --git a/src/assets/img/hero/2md.png b/src/assets/img/hero/2md.png new file mode 100644 index 0000000..3a6061a Binary files /dev/null and b/src/assets/img/hero/2md.png differ diff --git a/src/assets/img/hero/2sm.png b/src/assets/img/hero/2sm.png new file mode 100644 index 0000000..210322b Binary files /dev/null and b/src/assets/img/hero/2sm.png differ diff --git a/src/assets/theme/index.js b/src/assets/theme/index.js index 35bb412..fc89ebb 100644 --- a/src/assets/theme/index.js +++ b/src/assets/theme/index.js @@ -7,6 +7,7 @@ export const colours = { highlight: '#03a59e', roseDarker: '#FEB9B3', rose: '#F1CFCD', + roseLight: '#F8E5E1', } colours.text = colours.offwhite diff --git a/src/components/Button/index.js b/src/components/Button/index.js new file mode 100644 index 0000000..0ef7c4c --- /dev/null +++ b/src/components/Button/index.js @@ -0,0 +1,35 @@ +import styled from 'styled-components' +import { colours } from '../../assets/theme' + +const Button = styled.button` + background-color: transparent; + border: 1px solid ${colours.midnightDarker}; + padding: 0.3em 1em; + font-family: Karla; + font-weight: inherit; + color: ${colours.midnightDarker}; + opacity: 1; + text-decoration: none; + font-size: 24px; + cursor: pointer; + width: 100%; + + svg { + margin-right: 12px; + position: relative; + top: 2px; + } + + :hover { + background-color: ${colours.midnightDarker}; + color: ${colours.roseLight}; + + svg { + path { + stroke: ${colours.roseLight}; + } + } + } +` + +export default Button diff --git a/src/components/Info/index.js b/src/components/Info/index.js index 352e576..901da88 100644 --- a/src/components/Info/index.js +++ b/src/components/Info/index.js @@ -2,7 +2,8 @@ import { h, Fragment } from 'preact' import { isFuture, isPast } from 'date-fns' -import { P } from '../Text' +import { H1 } from '../Text' +import Markdown from '../Markdown' import translations from '../../data/strings' import InfoLayout from '../InfoLayout' import { @@ -10,8 +11,15 @@ import { Title, InfoContent, PositionedCross as CrossSvg, + TopContent, } from './styles' +import intro from '../../data/intro.md' +import credits from '../../data/credits.md' +import Button from '../Button' +import PlaySvg from '../Svg/Play' +import { colours } from '../../assets/theme' + const Info = ({ data, loading, infoActive, setInfoActive, currentVideo }) => { const pastStreams = data && data.length @@ -20,20 +28,34 @@ const Info = ({ data, loading, infoActive, setInfoActive, currentVideo }) => { const futureStreams = data && data.length - ? data.filter( - feeditem => - feeditem.id !== (currentVideo && currentVideo.id) && - isFuture(new Date(feeditem.start)) - ) + ? data + .filter( + feeditem => + feeditem.id !== (currentVideo && currentVideo.id) && + isFuture(new Date(feeditem.start)) + ) + .sort( + (a, b) => + // Turn your strings into dates, and then subtract them + // to get a value that is either negative, positive, or zero. + new Date(a.start) - new Date(b.start) + ) : [] - console.log({ currentVideo }) - return ( {infoActive && setInfoActive(false)} />} {!loading && ( - + + +

The Para-Real:

+

Finding the Future in Unexpected Places

+ {intro} + +
{currentVideo && ( {translations.en.nowPlaying}: @@ -58,7 +80,10 @@ const Info = ({ data, loading, infoActive, setInfoActive, currentVideo }) => { ))} ) : null} -
+ + {credits} + + )}
) diff --git a/src/components/Info/styles.js b/src/components/Info/styles.js index 6d73930..abf9c6d 100644 --- a/src/components/Info/styles.js +++ b/src/components/Info/styles.js @@ -1,7 +1,7 @@ import { format } from 'date-fns' import { h, Fragment } from 'preact' import styled from 'styled-components' -import { colours } from '../../assets/theme' +import { colours, textSizes } from '../../assets/theme' import config from '../../data/config' import Logo from '../Logo' import translations from '../../data/strings' @@ -10,6 +10,8 @@ import CrossSvg from '../Svg/Cross' import { P, H1, H2, Span, Label } from '../Text' import Link from '../Link' import { bool, instanceOf, string } from 'prop-types' +import Markdown from '../Markdown' +import Button from '../Button' export const Wrapper = styled.div` height: 100vh; @@ -34,10 +36,21 @@ export const Wrapper = styled.div` } ` -export const Top = styled.div`` - export const InfoContent = styled.div` + max-width: 600px; + margin: 0 0 0em 2px; padding-bottom: 1em; + + h1 { + display: none; + + &:last-of-type { + margin-bottom: 32px; + } + @media screen and (max-width: 1000px) { + display: block; + } + } ` export const PositionedLogo = styled(Logo)` @@ -68,18 +81,22 @@ export const PositionedCross = styled(CrossSvg)` } ` -const VCWrapper = styled.div` +export const VCWrapper = styled.div` max-width: 600px; margin: 0 0 6em 2px; - border-left: 5px solid ${colours.midnightDarker}; - padding-left: 1em; + + button { + margin-top: 16px; + } + /* border-left: 5px solid ${colours.midnightDarker}; */ + /* padding-left: 1em; */ ` const VCImg = styled.img` width: 100%; ` -const ItemTitle = styled(H2)` +const ItemTitleWrapper = styled.div` margin-bottom: 0.3em; ` @@ -93,46 +110,49 @@ const LinkBlock = styled(Link)` width: 100%; ` +const renderTitles = titles => + titles.split('\\n').map(title =>

{title}

) + export const VideoCard = ({ title, description, start, - end, previewPath, hasPassed, videoUrl, -}) => ( - - {videoUrl && hasPassed ? ( - - {title} - - - ) : ( - - {title} - - - )} - - {`${ - hasPassed - ? translations.en.streamDatePast - : translations.en.streamDateFuture - }`} - - {format(new Date(start), 'hh:mm dd/MM/yy')} - - -

{description}

-
-) +}) => { + console.log('start', start) + return ( + + + {`${hasPassed ? translations.en.streamDatePast : ''}`} + + {hasPassed + ? format(new Date(start), 'dd/MM/yy') + : format(new Date(start), 'do LLLL y // HH:mm')} + + + {videoUrl && hasPassed ? ( + + {renderTitles(title)} + + + ) : ( + + {renderTitles(title)} + + + )} +

{description}

+ +
+ ) +} VideoCard.propTypes = { title: string, description: string, start: instanceOf(Date), - end: instanceOf(Date), previewPath: string, hasPassed: bool, videoUrl: string, diff --git a/src/components/InfoLayout/index.js b/src/components/InfoLayout/index.js index aca8c24..45c935d 100644 --- a/src/components/InfoLayout/index.js +++ b/src/components/InfoLayout/index.js @@ -2,47 +2,58 @@ import { h } from 'preact' import { useEffect, useRef, useState } from 'preact/hooks' import { bool, string } from 'prop-types' -import { H1, H2 } from '../Text' +import { H1, H2, Label, Span } from '../Text' import { Wrapper, - PositionedLogo as Logo, TaglineContainer, LoaderWrapper, Content, - Fade, + Hero, + FadeBottom, } from './styles' import translations from '../../data/strings' import { colours } from '../../assets/theme' import Loader from '../Loader' import { useTimeout } from '../../hooks/timerHooks' +import { NdcLogo, RFLogo } from '../Logo' -const InfoLayout = ({ children, loading }) => { - return ( - - - - - - {loading ? ( - - - - ) : ( - children - )} - - +const InfoLayout = ({ children, loading }) => ( + + + {loading ? ( + + + + ) : ( + children + )} + + +
+

The

+

Para-

+

Real

+

+ Finding the Future in Unexpected Places +

+
- {translations && - translations.en.underscoreTagline.map(line => ( -

- {line} -

- ))} + + + + + + +
-
- ) -} + + +
+) InfoLayout.propTypes = { loading: bool, diff --git a/src/components/InfoLayout/styles.js b/src/components/InfoLayout/styles.js index 05dca9c..73af966 100644 --- a/src/components/InfoLayout/styles.js +++ b/src/components/InfoLayout/styles.js @@ -1,25 +1,20 @@ import styled from 'styled-components' import { colours } from '../../assets/theme' +import bg from '../../assets/img/hero/2md.png' import { H1 } from '../Text' import Logo from '../Logo' +const heroWidth = 'calc(100vw - 600px - 4em)' + export const Wrapper = styled.div` height: 100vh; width: 100vw; - padding: 6em 2em 2em 2em; + padding: 2em; display: flex; - /* justify-content: space-between; */ - /* flex-direction: column; */ - - background: url(https://images.unsplash.com/photo-1579762715118-a6f1d4b934f1?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=3031&q=80) - ${colours.rose}; + background-color: ${colours.roseLight}; box-sizing: border-box; - background-size: cover; - background-position-y: 50%; - background-position-x: 23vw; - background-blend-mode: soft-light; position: fixed; overflow-y: scroll; @@ -30,14 +25,10 @@ export const Wrapper = styled.div` } @media screen and (max-width: 1200px) { - padding: 6em 1.5em 1.5em 1.5em; - background-size: 150%; - background-position-x: 0vw; + padding: 1.5em; } @media screen and (max-width: 800px) { - padding: 6em 1em 1em 1em; - background-size: 250%; - background-position-x: -50vw; + padding: 1em; } ` @@ -45,9 +36,14 @@ export const Top = styled.div` width: 50%; ` -const gradientColour = '#F8E5E2' -const getGradient = direction => - `linear-gradient(to ${direction}, ${gradientColour}ee 0%,${gradientColour}00 100%);` +const gradientColourLight = '#F8E5E2' +const gradientColourDark = colours.midnightDarker +const getGradient = (direction, lightDark) => + `linear-gradient(to ${direction}, ${ + lightDark === 'dark' ? gradientColourDark : gradientColourLight + }ee 0%,${ + lightDark === 'dark' ? gradientColourDark : gradientColourLight + }00 100%);` // prettier-ignore export const Fade = styled.div` @@ -59,6 +55,44 @@ export const Fade = styled.div` left: 0; background: ${getGradient('bottom')}; ` + +export const Hero = styled.div` + width: ${heroWidth}; + height: 100vh; + background: url(${bg}); + background-size: cover; + background-position-x: right; + position: fixed; + padding: 0; + right: 0; + top: 0; + padding: 2em 2em 8px 2em; + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: space-between; + pointer-events: none; + h1, + h2 { + color: ${colours.offwhite}; + } + + h1:not(:last-of-type) { + font-size: 30vh; + + @media screen and (max-height: 600px) { + font-size: 20vh; + } + @media screen and (max-width: 1200px) { + font-size: 20vh; + } + } + + @media screen and (max-width: 1000px) { + display: none; + } +` + export const LoaderWrapper = styled.div` display: flex; justify-content: center; @@ -66,7 +100,7 @@ export const LoaderWrapper = styled.div` height: 100vh; position: fixed; top: 0; - width: 50vw; + width: 600px; ` export const Content = styled.div` @@ -75,27 +109,37 @@ export const Content = styled.div` export const PositionedLogo = styled(Logo)`` -export const TaglineContainer = styled.div` - background: ${getGradient('top')}; +export const FadeBottom = styled.div` + background: ${getGradient('top', 'dark')}; + width: ${heroWidth}; position: fixed; bottom: 0em; padding-bottom: 0.5em; - right: 1em; + right: 0; + /* left: 0; */ pointer-events: none; + min-height: 75px; - @media screen and (max-width: 1200px) { - width: 100vw; - right: auto; - left: 1.5em; - h1 { - font-size: 32px; - text-align: left; - } - } - - @media screen and (max-width: 800px) { + /* @media screen and (max-width: 800px) { h1 { font-size: 24px; } + } */ +` + +export const TaglineContainer = styled.div` + width: 100%; + bottom: 0em; + padding-bottom: 0.5em; + right: 0; + + display: flex; + align-items: center; + justify-content: space-between; + position: relative; + z-index: 2; + + a { + pointer-events: all; } ` diff --git a/src/components/Logo/index.js b/src/components/Logo/index.js index aabbcfc..9d23c1a 100644 --- a/src/components/Logo/index.js +++ b/src/components/Logo/index.js @@ -3,12 +3,11 @@ import { bool, string } from 'prop-types' import styled from 'styled-components' import { colours } from '../../assets/theme' -const Logo = ({ colour = colours.offwhite, active, ...rest }) => ( +const Logo = ({ colour = colours.offwhite, ...rest }) => ( ( ) const LogoSvg = styled.svg` - width: 200px; + height: 60px; ` Logo.propTypes = { @@ -27,4 +26,32 @@ Logo.propTypes = { active: bool, } +export const NdcLogo = ({ colour = colours.offwhite, ...rest }) => ( + + + +) + +export const RFLogo = ({ colour = colours.offwhite, ...rest }) => ( + + + +) + export default Logo diff --git a/src/components/Markdown/index.js b/src/components/Markdown/index.js new file mode 100644 index 0000000..2c430a9 --- /dev/null +++ b/src/components/Markdown/index.js @@ -0,0 +1,49 @@ +/* eslint-disable jsx-a11y/anchor-has-content */ +import { h, Fragment } from 'preact' +import MarkdownRenderer from 'markdown-to-jsx' +import { MarkdownWrapper } from './styles' +import { P, A, H1, H2, Span } from '../Text' + +const Markdown = ({ children, withLinebreaks, options, ...rest }) => ( + + ( + {spanChildren} + ), + }, + a: { + component: props => ( + + ), + }, + h1: { + component: H1, + }, + h2: { + component: H2, + }, + }, + ...options, + }} + {...rest} + > + {children} + + +) + +export default Markdown diff --git a/src/components/Markdown/styles.js b/src/components/Markdown/styles.js new file mode 100644 index 0000000..3dbe263 --- /dev/null +++ b/src/components/Markdown/styles.js @@ -0,0 +1,40 @@ +import { h } from 'preact' +import styled from 'styled-components' +import { colours } from '../../assets/theme' +import { P } from '../Text' + +export const MarkdownWrapper = styled.span` + a { + color: ${colours.highlight}; + } + + img { + max-width: 200px; + float: left; + padding: 0 12px 6px 0; + } + + h1 { + margin-bottom: 1em; + } + + h2 { + margin-bottom: 0.5em; + } + + p { + margin-bottom: ${props => (props.$withLinebreaks ? '32px' : '0')}; + } + + p > p { + display: inline; + margin-bottom: 0; + } + + li { + margin-left: 1em; + } + ul { + margin-bottom: 1em; + } +` diff --git a/src/components/Svg/Play.js b/src/components/Svg/Play.js new file mode 100644 index 0000000..68264ab --- /dev/null +++ b/src/components/Svg/Play.js @@ -0,0 +1,19 @@ +import { h } from 'preact' +import { number, string } from 'prop-types' + +const Play = ({ size = '24', colour = 'inherit', ...rest }) => ( + + + +) + +Play.propTypes = { + size: number, + colour: string, +} + +export default Play diff --git a/src/components/Text/index.js b/src/components/Text/index.js index 5f89fb3..e08f9e7 100644 --- a/src/components/Text/index.js +++ b/src/components/Text/index.js @@ -13,7 +13,9 @@ const Text = ({ fontFamily = 'Karla', children, size, + sizeUnit, selectable = true, + fontStyle, ...rest }) => { const colour = colourProp || colours.text @@ -26,6 +28,8 @@ const Text = ({ lineHeight={lineHeight} $fontFamily={fontFamily} $size={size} + $sizeUnit={sizeUnit} + $fontStyle={fontStyle} selectable={selectable} {...rest} > @@ -61,20 +65,18 @@ Text.propTypes = { selectable: bool, } -export const H1 = ({ children, ...rest }) => { - return ( - - {children} - - ) -} +export const H1 = ({ children, size, ...rest }) => ( + + {children} + +) export const H2 = ({ children, ...rest }) => ( ( ) export const Span = ({ children, ...rest }) => ( - + {children} ) diff --git a/src/components/Text/styles.js b/src/components/Text/styles.js index b225fac..d39503d 100644 --- a/src/components/Text/styles.js +++ b/src/components/Text/styles.js @@ -4,6 +4,7 @@ import { colours } from '../../assets/theme' export const TextBase = styled.span` ${({ $size, + $fontStyle, weight, colour, align, @@ -12,6 +13,7 @@ export const TextBase = styled.span` $fontFamily: fontFamily, selectable, underline, + $sizeUnit, }) => css` font-family: ${fontFamily}; font-weight: ${weight}; @@ -21,7 +23,8 @@ export const TextBase = styled.span` opacity: ${opacity}; user-select: ${selectable ? 'inherit' : 'none'}; text-decoration: ${underline ? 'underline' : 'none'}; - font-size: ${$size}px; + font-size: ${`${$size}${$sizeUnit || 'px'}`}; + font-style: ${$fontStyle}; ::selection { background-color: ${colours.midnightDarker}; diff --git a/src/data/credits.md b/src/data/credits.md new file mode 100644 index 0000000..41fb5d7 --- /dev/null +++ b/src/data/credits.md @@ -0,0 +1,23 @@ +## Credits + +A [NEW DESIGN CONGRESS](https://newdesigncongress.org) project + +Stream Design by **[BENJAMIN JONES](https://benjaminjon.es)** + +Host & Research by **[CADE DIEHM](https://shiba.computer)** + +Stream Backgrounds by **[IGNATIUS GILFEDDER](https://ignatius.design)** + +Series operated by **[ReclaimFutures](https://reclaimfutures.org)** + +Infrastructure by **[UNDERSCO.RE](https://undersco.re)** + +Music \*\*\*\*by: + +- [♥ GOJII ♥](https://gojii.bandcamp.com/) +- [音 LIGHT システム](https://saphronsquares.bandcamp.com/) +- [ABELARD](https://abelard.bandcamp.com/) +- [Frog of Earth](https://frogoftheearth.bandcamp.com/) +- [spatial manufacture ltd.](https://spatialmanufactureltd.bandcamp.com/)\*\* + +Simulcasted at [UNDERSCORE TV](https://stream.undersco.re) and [Twitch](https://twitch.tv/newdesigncongress) diff --git a/src/data/intro.md b/src/data/intro.md new file mode 100644 index 0000000..f696ae6 --- /dev/null +++ b/src/data/intro.md @@ -0,0 +1,7 @@ +[NEW DESIGN CONGRESS](https://newdesigncongress.org) x [RECLAIMFUTURES](https://reclaimfutures.org) present _The Para-Real: Finding the Future in Unexpected Places,_ a Spring/Summer livestream series about economic subcultures and emergent technology use. Over 12 episodes streamed weekly, we meet filmmakers who have never met their actors, artists building their own networks of value, documentarians exploring digital identity, and members of resilient subcultures. All of these people share a commonality: they have an innate understanding of the _Para-Real,_ and have seized upon it to better their surroundings. + +Between the digital realm and our physical world is a third space, equally material but poorly understood. The _Para-Real_ is a tangible place where class structures, economics and the outcomes of hardware and infrastructure design collide. The Para-Real manifests in many ways — It can be the desire for play that turns young Minecraft players into network administrators, the drive that fosters the resilience of subculture-driven mutually-supportive marketplaces, or the tension of class structures inherent in Virtual Reality's encroachment on living room space. + +_We shape our tools, and thereafter our tools shape us._ + +The Para-Real is everywhere. Whether attending a Furcon at the height of pandemic, organising community for resilience, or navigating a commissioned open world, we must consider ourselves as inhabiting not a real or online world, but a bridge between the two, one whose rules are not yet settled. _The future is not a Zoom call_. The digital systems we are confined to today merge protocol with platform to prey on isolation and extract value from labour. That we grapple with this incarnation of the digital realm indicates a dominant cartel in decline. In its place is a vacuum. We must resist the immature groupthink of the 90s’ vision of what the Internet can be — the Para-Real is a contested space*. Never trust someone who says the Internet is boring.* Thanks to the Para-Real, the Internet has never been weirder. diff --git a/src/data/strings.js b/src/data/strings.js index f888fe7..1f43059 100644 --- a/src/data/strings.js +++ b/src/data/strings.js @@ -1,11 +1,13 @@ export default { en: { - nextStream: 'Next streams', + nextStream: 'Program', pastStream: 'Latest streams', nowPlaying: 'Now playing', noStreams: 'No upcoming streams, check back soon.', underscoreTagline: ['LEAVE THE', 'SURVEILLANCE ECONOMY', '— TOGETHER.'], streamDateFuture: 'Going live at: ', streamDatePast: 'First broadcast: ', + main_text: + "[NEW DESIGN CONGRESS](https://newdesigncongress.org) x [RECLAIMFUTURES](https://reclaimfutures.org) present *The Para-Real: Finding the Future in Unexpected Places,* a Spring/Summer livestream series about economic subcultures and emergent technology use. Over 12 episodes streamed weekly, we meet filmmakers who have never met their actors, artists building their own networks of value, documentarians exploring digital identity, and members of resilient subcultures. All of these people share a commonality: they have an innate understanding of the *Para-Real,* and have seized upon it to better their surroundings. \nBetween the digital realm and our physical world is a third space, equally material but poorly understood. The *Para-Real* is a tangible place where class structures, economics and the outcomes of hardware and infrastructure design collide. The Para-Real manifests in many ways — It can be the desire for play that turns young Minecraft players into network administrators, the drive that fosters the resilience of subculture-driven mutually-supportive marketplaces, or the tension of class structures inherent in Virtual Reality's encroachment on living room space.\n\nWe shape our tools, and thereafter our tools shape us.* The Para-Real is everywhere. Whether attending a Furcon at the height of pandemic, organising community for resilience, or navigating a commissioned open world, we must consider ourselves as inhabiting not a real or online world, but a bridge between the two, one whose rules are not yet settled. *The future is not a Zoom call*. The digital systems we are confined to today merge protocol with platform to prey on isolation and extract value from labour. That we grapple with this incarnation of the digital realm indicates a dominant cartel in decline. In its place is a vacuum. We must resist the immature groupthink of the 90s’ vision of what the Internet can be — the Para-Real is a contested space*. Never trust someone who says the Internet is boring.* Thanks to the Para-Real, the Internet has never been weirder.", }, } diff --git a/src/hooks/data.js b/src/hooks/data.js index b18dd63..52a7f46 100644 --- a/src/hooks/data.js +++ b/src/hooks/data.js @@ -24,7 +24,6 @@ export const useEventStream = () => { calEvents.map(async calItem => { const url = calItem.component.getAllProperties('url')[0] if (url) { - console.log('url', url) const id = url .getFirstValue() .split('/') @@ -61,6 +60,14 @@ export const useEventStream = () => { videoUrl: url.getFirstValue(), } setData(arr => [...arr, item]) + } else { + const item = { + title: calItem.summary, + description: calItem.description, + start: calItem.startDate.toJSDate(), + end: calItem.endDate.toJSDate(), + } + setData(arr => [...arr, item]) } }) )