| @@ -1,7 +1,4 @@ | |||
| <div class="site"> | |||
| <header> | |||
| <app-header></app-header> | |||
| </header> | |||
| <main> | |||
| <app-news-dashboard></app-news-dashboard> | |||
| </main> | |||
| @@ -1,14 +1,13 @@ | |||
| @import "../variables.scss"; | |||
| main { | |||
| padding-left: $footer-height; | |||
| padding-top: $footer-height; | |||
| padding: 0px; | |||
| margin: 0px; | |||
| padding-bottom: $footer-height; | |||
| } | |||
| .site { | |||
| height: auto; | |||
| min-height: 100%; | |||
| } | |||
| footer { | |||
| @@ -1,5 +1,6 @@ | |||
| import { BrowserModule } from '@angular/platform-browser'; | |||
| import { NgModule } from '@angular/core'; | |||
| import { FormsModule } from '@angular/forms'; | |||
| import { AppRoutingModule } from './app-routing.module'; | |||
| import { AppComponent } from './app.component'; | |||
| @@ -12,6 +13,7 @@ import { AllNewsDashboardComponent } from './news/all-news-dashboard/all-news-da | |||
| import { TabsComponent } from './misc/tab/tabs/tabs.component'; | |||
| import { TabComponent } from './misc/tab/tab/tab.component'; | |||
| import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; | |||
| import { NewsTeaserComponent } from './news/news-teaser/news-teaser.component'; | |||
| @NgModule({ | |||
| @@ -24,8 +26,10 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; | |||
| AllNewsDashboardComponent, | |||
| TabsComponent, | |||
| TabComponent, | |||
| NewsTeaserComponent, | |||
| ], | |||
| imports: [ | |||
| FormsModule, | |||
| BrowserModule.withServerTransition({ appId: 'serverApp' }), | |||
| AppRoutingModule, | |||
| HttpClientModule, | |||
| @@ -4,7 +4,7 @@ | |||
| padding-left: 0.5%; | |||
| padding-top: 0.5%; | |||
| padding-bottom: 0.5%; | |||
| background-color: $primary-color; | |||
| background: transparent; | |||
| margin: 0px; | |||
| font-size: 20px; | |||
| text-align: left; | |||
| @@ -1,4 +0,0 @@ | |||
| <div class="header"> | |||
| <fa-icon [icon]="faNewspaper"></fa-icon> | |||
| News Page | |||
| </div> | |||
| @@ -2,7 +2,7 @@ | |||
| .header { | |||
| color: white; | |||
| background: linear-gradient(48deg, $detail-color, $secondary-color, $primary-color); | |||
| background: transparent; | |||
| margin: 0px; | |||
| font-size: 30px; | |||
| padding: 1em 1em; | |||
| @@ -138,3 +138,8 @@ | |||
| .inactive { | |||
| display: none; | |||
| } | |||
| .pane { | |||
| width: 100%; | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| import { Component, OnInit, ContentChildren, Input } from '@angular/core'; | |||
| import { Component, OnInit, ContentChildren, Input, ViewChildren, TemplateRef } from '@angular/core'; | |||
| import { TabsComponent } from '../tabs/tabs.component'; | |||
| import { Template } from '@angular/compiler/src/render3/r3_ast'; | |||
| @Component({ | |||
| templateUrl: 'tab.component.html', | |||
| @@ -1,15 +1,21 @@ | |||
| <div class="tab-container"> | |||
| <div class="flex row"> | |||
| <div | |||
| <div class="flex-container header"> | |||
| <p class="header-icon"> | |||
| <fa-icon [icon]="faNewspaper"></fa-icon> | |||
| </p> | |||
| <p class="title">News Page</p> | |||
| <p class="spacer"></p> | |||
| <p class="spacer"></p> | |||
| <p | |||
| *ngFor="let tab of tabs" | |||
| (click)="selectTab(tab)" | |||
| [class.active]="tab?.active" | |||
| class="flex col tab-title" | |||
| class="tab-title" | |||
| > | |||
| <p>{{ tab?.title }}</p> | |||
| </div> | |||
| {{ tab?.title }} | |||
| </p> | |||
| </div> | |||
| <div class="tab-content flex row" > | |||
| <ng-content></ng-content> | |||
| <div class="tab-content flex flex-row"> | |||
| <ng-content></ng-content> | |||
| </div> | |||
| </div> | |||
| @@ -1,35 +1,51 @@ | |||
| $border-radius: 12.5px; | |||
| @import "../../../../variables.scss"; | |||
| .tab-title { | |||
| min-width: 6em; | |||
| cursor: pointer; | |||
| padding-left: 5px; | |||
| padding-right: 5px; | |||
| border-top: solid $border-color 1px; | |||
| margin-bottom: 0px; | |||
| border-top-left-radius: 12.5px; | |||
| border-top-right-radius: 12.5px; | |||
| background-color: $primary-color; | |||
| opacity: 0.4; | |||
| transition: 0.3s; | |||
| padding: 1em; | |||
| } | |||
| .title { | |||
| justify-self: flex-start; | |||
| align-items: self-start; | |||
| } | |||
| .tab-title:active, .active { | |||
| .tab-title:active, | |||
| .active { | |||
| opacity: 1; | |||
| } | |||
| .tab-title:hover { | |||
| opacity: 0.8 | |||
| opacity: 0.8; | |||
| } | |||
| .tab-content { | |||
| border: 5px solid $primary-color; | |||
| border-top-right-radius: 12.5px; | |||
| border-top-right-radius: 12.5px; | |||
| border-bottom-left-radius: 12.5px; | |||
| border-bottom-right-radius: 12.5px; | |||
| padding-left: 1em; | |||
| height: 100%; | |||
| } | |||
| .tab-container { | |||
| border-radius: $border-radius; | |||
| background: white; | |||
| } | |||
| .header-icon { | |||
| padding: 1em; | |||
| } | |||
| .header { | |||
| justify-content: space-between; | |||
| align-items: spac; | |||
| height: 3em; | |||
| flex-direction: row nowrap; | |||
| font-size: $header-font-size; | |||
| color: white; | |||
| background: linear-gradient(48deg, $detail-color, $secondary-color, $primary-color); | |||
| } | |||
| @@ -1,5 +1,7 @@ | |||
| import { Component, OnInit, ContentChildren, QueryList, AfterContentInit } from '@angular/core'; | |||
| import { TabComponent } from '../tab/tab.component'; | |||
| import { faNewspaper, faFilter } from '@fortawesome/free-solid-svg-icons'; | |||
| import { from } from 'rxjs'; | |||
| @Component({ | |||
| selector: 'app-tabs', | |||
| @@ -7,7 +9,7 @@ import { TabComponent } from '../tab/tab.component'; | |||
| styleUrls: ['./tabs.component.scss'] | |||
| }) | |||
| export class TabsComponent implements OnInit, AfterContentInit { | |||
| faNewspaper = faNewspaper; | |||
| @ContentChildren(TabComponent) tabs: QueryList<TabComponent>; | |||
| @ContentChildren(TabComponent) activeTab: TabComponent; | |||
| public selectTab(tab: TabComponent): void { | |||
| @@ -22,7 +24,6 @@ export class TabsComponent implements OnInit, AfterContentInit { | |||
| } | |||
| protected getTabAnimationDirection(tab: TabComponent): 'right' | 'left' | 'none' { | |||
| debugger; | |||
| const activeTab = this.tabs.toArray().filter(tab => tab.active)[0]; | |||
| if (activeTab === undefined) { | |||
| return 'none'; | |||
| @@ -37,6 +38,10 @@ export class TabsComponent implements OnInit, AfterContentInit { | |||
| return 'none'; | |||
| } | |||
| getActiveTab(): TabComponent { | |||
| return this.tabs.toArray().filter(tab => tab.active)[0]; | |||
| } | |||
| ngAfterContentInit(): void { | |||
| // get all active tabs | |||
| this.tabs.toArray().forEach((tab: TabComponent, index) => tab.tabIndex = index); | |||
| @@ -1,10 +0,0 @@ | |||
| export class TopHeadlineSearchDto { | |||
| sources: string; | |||
| q: string; | |||
| qInTitle: string; | |||
| domains: string; | |||
| excludeDomains: string; | |||
| from: Date; | |||
| to: Date; | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| import { SortBy } from '../enum/sort-by'; | |||
| export class NewsSearchDto { | |||
| sources?: string; | |||
| q?: string; | |||
| qInTitle?: string; | |||
| domains?: string; | |||
| excludeDomains?: string; | |||
| from?: Date; | |||
| to?: Date; | |||
| sortBy?: SortBy; | |||
| pageSize?: number; | |||
| page?: number; | |||
| apiKey: string; | |||
| } | |||
| @@ -0,0 +1,5 @@ | |||
| export enum SortBy { | |||
| 'Relevancy' = 'relevancy', | |||
| 'Popularity' = 'popularity', | |||
| 'PublishedAt' = 'publishedAt', | |||
| } | |||
| @@ -1 +1,5 @@ | |||
| <p>all-news-dashboard works!</p> | |||
| <div class="flex-container wrap"> | |||
| <div class="flex-item" *ngFor="let article of news"> | |||
| <app-news-teaser [article]="article"></app-news-teaser> | |||
| </div> | |||
| </div> | |||
| @@ -1,4 +1,7 @@ | |||
| import { Component, OnInit } from '@angular/core'; | |||
| import { NewsService } from '../../service/news.service'; | |||
| import { Article } from '../../models/model/article'; | |||
| import { apiKey } from '../../../environments/.api-key'; | |||
| @Component({ | |||
| selector: 'app-all-news-dashboard', | |||
| @@ -7,9 +10,16 @@ import { Component, OnInit } from '@angular/core'; | |||
| }) | |||
| export class AllNewsDashboardComponent implements OnInit { | |||
| constructor() { } | |||
| news: Article[]; | |||
| constructor(protected newsService: NewsService) { } | |||
| ngOnInit(): void { | |||
| // this.newsService.searchAllNews({ | |||
| // apiKey | |||
| // }).subscribe(response => { | |||
| // this.news = response.articles; | |||
| // }); | |||
| } | |||
| } | |||
| @@ -1,8 +1,8 @@ | |||
| <app-tabs> | |||
| <app-tab title="Top News"> | |||
| <app-tab style="width: auto" title="Top News"> | |||
| <app-top-news-dashboard></app-top-news-dashboard> | |||
| </app-tab> | |||
| <app-tab title="All News"> | |||
| <app-tab style="width: auto" title="All News"> | |||
| <app-all-news-dashboard></app-all-news-dashboard> | |||
| </app-tab> | |||
| </app-tabs> | |||
| @@ -0,0 +1,9 @@ | |||
| @import "../../../variables.scss"; | |||
| $border-radius: 12.5px; | |||
| .dashboard { | |||
| padding: 0px; | |||
| background: white; | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| <div | |||
| (click)="open()" | |||
| class="teaser" | |||
| *ngIf="article.urlToImage && article.content" | |||
| > | |||
| <div class="teaser-header"> | |||
| <div class="teaser-title">{{ article.title }}</div> | |||
| <div class="teaser-img"> | |||
| <img | |||
| [src]="article.urlToImage" | |||
| *ngIf="article.urlToImage" | |||
| alt="image of article" | |||
| /> | |||
| </div> | |||
| </div> | |||
| <div class="teaser-content" *ngIf="article.description"> | |||
| {{ article.description }} ... <a [href]="article.url"> Read</a> | |||
| </div> | |||
| </div> | |||
| @@ -0,0 +1,86 @@ | |||
| @import "../../../variables.scss"; | |||
| $teaser-border-radius: 12.5px; | |||
| $teaser-background: linear-gradient(48deg, rgb(223, 217, 217), white); | |||
| .teaser-header { | |||
| min-height: 10em; | |||
| flex-direction: column; | |||
| > div.teaser-img { | |||
| border-top-left-radius: $teaser-border-radius; | |||
| border-top-right-radius: $teaser-border-radius; | |||
| background: $teaser-background; | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| > img { | |||
| padding: 5px; | |||
| max-width: 40em; | |||
| max-height: 20em; | |||
| } | |||
| } | |||
| > div.teaser-title { | |||
| padding: 2em; | |||
| background: transparent; | |||
| max-width: 40em; | |||
| text-align: center; | |||
| font-size: $header-font-size; | |||
| } | |||
| > div.teaser-title::after { | |||
| content: ""; | |||
| background-color: $detail-color; | |||
| display: block; | |||
| margin: 2rem 0; | |||
| height: 2px; | |||
| width: 3.75rem; | |||
| } | |||
| } | |||
| .teaser { | |||
| cursor: pointer; | |||
| border: 1px solid $border-color; | |||
| width: 45em; | |||
| min-height: 20em; | |||
| border-radius: $teaser-border-radius; | |||
| } | |||
| @-webkit-keyframes hover { | |||
| 100% { | |||
| border: 1px white; | |||
| } | |||
| } | |||
| @keyframes hover { | |||
| 100% { | |||
| border: 1px solid white; | |||
| } | |||
| } | |||
| .teaser:hover { | |||
| -webkit-animation: hover 0.3s cubic-bezier(0.39, 0.575, 0.565, 1) both; | |||
| animation: hover 0.3s cubic-bezier(0.39, 0.575, 0.565, 1) both; | |||
| } | |||
| .teaser-content { | |||
| background: $teaser-background; | |||
| border-bottom-left-radius: $teaser-border-radius; | |||
| border-bottom-right-radius: $teaser-border-radius; | |||
| min-width: 10em; | |||
| padding: 5px; | |||
| } | |||
| .teaser-footer { | |||
| height: 2em; | |||
| padding: 5px; | |||
| } | |||
| .btn { | |||
| border-radius: 2.5px; | |||
| background-color: $button-color; | |||
| color: white; | |||
| padding: 5px; | |||
| } | |||
| a { | |||
| color: $detail-color; | |||
| text-decoration: none; | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| import { async, ComponentFixture, TestBed } from '@angular/core/testing'; | |||
| import { NewsTeaserComponent } from './news-teaser.component'; | |||
| describe('NewsTeaserComponent', () => { | |||
| let component: NewsTeaserComponent; | |||
| let fixture: ComponentFixture<NewsTeaserComponent>; | |||
| beforeEach(async(() => { | |||
| TestBed.configureTestingModule({ | |||
| declarations: [ NewsTeaserComponent ] | |||
| }) | |||
| .compileComponents(); | |||
| })); | |||
| beforeEach(() => { | |||
| fixture = TestBed.createComponent(NewsTeaserComponent); | |||
| component = fixture.componentInstance; | |||
| fixture.detectChanges(); | |||
| }); | |||
| it('should create', () => { | |||
| expect(component).toBeTruthy(); | |||
| }); | |||
| }); | |||
| @@ -0,0 +1,21 @@ | |||
| import { Component, OnInit, Input } from '@angular/core'; | |||
| import { Article } from '../../models/model/article'; | |||
| @Component({ | |||
| selector: 'app-news-teaser', | |||
| templateUrl: './news-teaser.component.html', | |||
| styleUrls: ['./news-teaser.component.scss'] | |||
| }) | |||
| export class NewsTeaserComponent implements OnInit { | |||
| @Input() article: Article; | |||
| constructor() { } | |||
| ngOnInit(): void { | |||
| } | |||
| open(): void { | |||
| window.open(this.article.url); | |||
| } | |||
| } | |||
| @@ -1 +1,68 @@ | |||
| <p>top-news-dashboard works!</p> | |||
| <div class="title flex-container"> | |||
| Top | |||
| <ng-container *ngIf="selectedCategory"> | |||
| {{ selectedCategory.key }} | |||
| </ng-container> | |||
| News | |||
| <ng-container *ngIf="selectedCountry" | |||
| >in {{ selectedCountry.key }} | |||
| <img | |||
| class="flag" | |||
| src="https://www.countryflags.io/{{ | |||
| selectedCountry.value | |||
| }}/shiny/64.png" | |||
| /></ng-container> | |||
| </div> | |||
| <div class="flex-container filter-bar" *ngIf="news"> | |||
| <div class="filter"> | |||
| <label for="search">Search: </label> | |||
| <input | |||
| (change)="upateDto()" | |||
| name="search" | |||
| id="search" | |||
| [(ngModel)]="searchDto.q" | |||
| /> | |||
| </div> | |||
| <div class="filter"> | |||
| <label for="country">Country: </label> | |||
| <select | |||
| (change)="upateDto()" | |||
| name="country" | |||
| id="country" | |||
| [(ngModel)]="selectedCountry" | |||
| > | |||
| <option | |||
| *ngFor="let country of CountryCodes | keyvalue" | |||
| [ngValue]="country" | |||
| > | |||
| {{ country.key }} | |||
| </option> | |||
| </select> | |||
| </div> | |||
| <div class="filter"> | |||
| <label for="category">Category: </label> | |||
| <select | |||
| (change)="upateDto()" | |||
| name="category" | |||
| id="category" | |||
| [(ngModel)]="selectedCategory" | |||
| > | |||
| <option *ngFor="let category of Category | keyvalue" [ngValue]="category"> | |||
| {{ category.key }} | |||
| </option> | |||
| </select> | |||
| </div> | |||
| </div> | |||
| <div class="flex-container wrap" *ngIf="news?.length; else notingFound"> | |||
| <div class="flex-item" *ngFor="let article of news"> | |||
| <app-news-teaser [article]="article"></app-news-teaser> | |||
| </div> | |||
| </div> | |||
| <ng-template #notingFound> | |||
| <div class="flex-container not-found"> | |||
| noting found | |||
| </div> | |||
| </ng-template> | |||
| @@ -0,0 +1,31 @@ | |||
| @import "../../../variables.scss"; | |||
| .filter-bar { | |||
| height: 5em; | |||
| margin: 2em; | |||
| display: flex; | |||
| flex-flow: row; | |||
| justify-content: space-evenly; | |||
| align-items: center; | |||
| border: 1px solid $border-color; | |||
| } | |||
| .filter { | |||
| height: 2em; | |||
| } | |||
| .title { | |||
| background: transparent; | |||
| font-size: $header-font-size; | |||
| } | |||
| .flag { | |||
| height: 1em; | |||
| padding-left: 0.5em; | |||
| align-self: center; | |||
| } | |||
| .not-found { | |||
| width: 160em; | |||
| height: 10em; | |||
| } | |||
| @@ -1,8 +1,10 @@ | |||
| import { Component, OnInit } from '@angular/core'; | |||
| import { Component, OnInit, ContentChild } from '@angular/core'; | |||
| import { Article } from '../../models/model/article'; | |||
| import { NewsService } from '../../service/news.service'; | |||
| import { apiKey } from '../../../environments/.api-key'; | |||
| import { CountryCode } from '../../models/enum/country-codes'; | |||
| import { TopHeadlineSearchDto } from '../../models/dto/top-headline-search.dto'; | |||
| import { Category } from 'src/app/models/enum/category'; | |||
| @Component({ | |||
| selector: 'app-top-news-dashboard', | |||
| @@ -10,16 +12,39 @@ import { CountryCode } from '../../models/enum/country-codes'; | |||
| styleUrls: ['./top-news-dashboard.component.scss'] | |||
| }) | |||
| export class TopNewsDashboardComponent implements OnInit { | |||
| news: Article[]; | |||
| searchDto: TopHeadlineSearchDto = { apiKey }; | |||
| selectedCountry: { key: string, value: CountryCode }; | |||
| selectedCategory: { key: string, value: Category }; | |||
| CountryCodes = CountryCode; | |||
| Category = Category; | |||
| constructor(protected newsService: NewsService) { } | |||
| ngOnInit(): void { | |||
| this.newsService.getTopNews({ | |||
| apiKey, | |||
| country: CountryCode.Germany | |||
| }).subscribe(response => { | |||
| upateDto(): void { | |||
| if (this.selectedCountry?.value) { | |||
| this.searchDto.country = this.selectedCountry.value; | |||
| } else { | |||
| delete this.searchDto.country; | |||
| } | |||
| if (this.selectedCategory?.value) { | |||
| this.searchDto.category = this.selectedCategory.value; | |||
| } else { | |||
| delete this.searchDto.category; | |||
| } | |||
| this.search(); | |||
| } | |||
| search(): void { | |||
| this.newsService.searchTopNews( | |||
| this.searchDto | |||
| ).subscribe(response => { | |||
| this.news = response.articles; | |||
| }); | |||
| } | |||
| ngOnInit(): void { | |||
| this.selectedCountry = { key: 'Germany', value: CountryCode.Germany }; | |||
| this.upateDto(); | |||
| } | |||
| } | |||
| @@ -1,10 +1,10 @@ | |||
| import { HttpClient } from '@angular/common/http'; | |||
| import { Injectable } from '@angular/core'; | |||
| import { HttpClient, HttpParams, HttpResponse, HttpHeaders } from '@angular/common/http'; | |||
| import { Observable } from 'rxjs'; | |||
| import { environment } from '../../environments/environment'; | |||
| import { TopHeadlineSearchDto } from '../models/dto/top-headline-search.dto'; | |||
| import { NewsSearchDto } from '../models/dto/news-search.dto'; | |||
| import { NewsResponse } from '../models/response/news-response'; | |||
| import { environment } from '../../environments/environment'; | |||
| import { Observable, Subject } from 'rxjs'; | |||
| import { map, tap } from 'rxjs/operators'; | |||
| @Injectable({ | |||
| providedIn: 'root' | |||
| @@ -13,9 +13,11 @@ export class NewsService { | |||
| constructor(protected http: HttpClient) { } | |||
| getTopNews(searchDto: TopHeadlineSearchDto): Observable<NewsResponse> | null { | |||
| return new Subject<NewsResponse>(); | |||
| const headers = new HttpHeaders(); | |||
| return this.http.get<NewsResponse>(`${environment.apiUrl}/top-headlines`, { params: searchDto as any, headers }); | |||
| searchTopNews(searchDto: TopHeadlineSearchDto): Observable<NewsResponse> | null { | |||
| return this.http.get<NewsResponse>(`${environment.apiUrl}/top-headlines`, { params: searchDto as any }); | |||
| } | |||
| searchAllNews(searchDto: NewsSearchDto): Observable<NewsResponse> | null { | |||
| return this.http.get<NewsResponse>(`${environment.apiUrl}/everything`, { params: searchDto as any }); | |||
| } | |||
| } | |||
| @@ -1,20 +1,52 @@ | |||
| @import "./variables.scss"; | |||
| .flex { | |||
| display: flex; | |||
| .container { | |||
| > .container { | |||
| flex-direction: column; | |||
| } | |||
| .col { | |||
| > .col { | |||
| flex-direction: column; | |||
| } | |||
| .row { | |||
| flex-direction: row; | |||
| } | |||
| .content { | |||
| > .content { | |||
| flex: 1; | |||
| } | |||
| } | |||
| .flex-row { | |||
| flex-direction: row; | |||
| } | |||
| .flex-container { | |||
| padding: 0; | |||
| margin: 0; | |||
| list-style: none; | |||
| display: -webkit-box; | |||
| display: -moz-box; | |||
| display: -ms-flexbox; | |||
| display: -moz-flex; | |||
| display: -webkit-flex; | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| } | |||
| .nowrap { | |||
| -webkit-flex-wrap: nowrap; | |||
| flex-wrap: nowrap; | |||
| } | |||
| .spacer { | |||
| flex-grow: 1; | |||
| } | |||
| .wrap { | |||
| -webkit-flex-wrap: wrap; | |||
| flex-wrap: wrap; | |||
| } | |||
| .flex-item { | |||
| padding: 5px; | |||
| } | |||
| .col > div { | |||
| margin: 10px; | |||
| padding: 20px; | |||
| @@ -27,8 +59,8 @@ | |||
| body, | |||
| html { | |||
| background-color: $base-color; | |||
| font-size: $default-font-size; | |||
| background: linear-gradient(48deg, $detail-color, $secondary-color, $primary-color); | |||
| height: 100%; | |||
| margin: 0px; | |||
| } | |||
| @@ -4,10 +4,17 @@ $primary-color: #2f85f6; | |||
| $secondary-color: #8c3cee; | |||
| $detail-color: #8c3cee; | |||
| $button-color: #8a8095; | |||
| $text-primary: white; | |||
| $border-color: white; | |||
| $border-color: #d8d8d8; | |||
| $footer-height: 1.2em; | |||
| $default-font-size: 15px; | |||
| $header-font-size: 25px; | |||